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,
42
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
43
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
44
from bzrlib.errors import (
48
from bzrlib.i18n import gettext
49
from bzrlib.inventory import Inventory
50
from bzrlib.lockable_files import LockableFiles
51
from bzrlib.smart import client, vfs, repository as smart_repo
52
from bzrlib.smart.client import _SmartClient
53
from bzrlib.revision import NULL_REVISION
54
from bzrlib.revisiontree import InventoryRevisionTree
55
from bzrlib.repository import RepositoryWriteLockResult, _LazyListJoin
56
from bzrlib.serializer import format_registry as serializer_format_registry
57
from bzrlib.trace import mutter, note, warning, log_exception_quietly
60
_DEFAULT_SEARCH_DEPTH = 100
63
class _RpcHelper(object):
64
"""Mixin class that helps with issuing RPCs."""
66
def _call(self, method, *args, **err_context):
68
return self._client.call(method, *args)
69
except errors.ErrorFromSmartServer, err:
70
self._translate_error(err, **err_context)
72
def _call_expecting_body(self, method, *args, **err_context):
74
return self._client.call_expecting_body(method, *args)
75
except errors.ErrorFromSmartServer, err:
76
self._translate_error(err, **err_context)
78
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
80
return self._client.call_with_body_bytes(method, args, body_bytes)
81
except errors.ErrorFromSmartServer, err:
82
self._translate_error(err, **err_context)
84
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
87
return self._client.call_with_body_bytes_expecting_body(
88
method, args, body_bytes)
89
except errors.ErrorFromSmartServer, err:
90
self._translate_error(err, **err_context)
93
def response_tuple_to_repo_format(response):
94
"""Convert a response tuple describing a repository format to a format."""
95
format = RemoteRepositoryFormat()
96
format._rich_root_data = (response[0] == 'yes')
97
format._supports_tree_reference = (response[1] == 'yes')
98
format._supports_external_lookups = (response[2] == 'yes')
99
format._network_name = response[3]
103
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
104
# does not have to be imported unless a remote format is involved.
106
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
107
"""Format representing bzrdirs accessed via a smart server"""
109
supports_workingtrees = False
112
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
113
# XXX: It's a bit ugly that the network name is here, because we'd
114
# like to believe that format objects are stateless or at least
115
# immutable, However, we do at least avoid mutating the name after
116
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
117
self._network_name = None
120
return "%s(_network_name=%r)" % (self.__class__.__name__,
123
def get_format_description(self):
124
if self._network_name:
126
real_format = controldir.network_format_registry.get(
131
return 'Remote: ' + real_format.get_format_description()
132
return 'bzr remote bzrdir'
134
def get_format_string(self):
135
raise NotImplementedError(self.get_format_string)
137
def network_name(self):
138
if self._network_name:
139
return self._network_name
141
raise AssertionError("No network name set.")
143
def initialize_on_transport(self, transport):
145
# hand off the request to the smart server
146
client_medium = transport.get_smart_medium()
147
except errors.NoSmartMedium:
148
# TODO: lookup the local format from a server hint.
149
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
150
return local_dir_format.initialize_on_transport(transport)
151
client = _SmartClient(client_medium)
152
path = client.remote_path_from_transport(transport)
154
response = client.call('BzrDirFormat.initialize', path)
155
except errors.ErrorFromSmartServer, err:
156
_translate_error(err, path=path)
157
if response[0] != 'ok':
158
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
159
format = RemoteBzrDirFormat()
160
self._supply_sub_formats_to(format)
161
return RemoteBzrDir(transport, format)
163
def parse_NoneTrueFalse(self, arg):
170
raise AssertionError("invalid arg %r" % arg)
172
def _serialize_NoneTrueFalse(self, arg):
179
def _serialize_NoneString(self, arg):
182
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
183
create_prefix=False, force_new_repo=False, stacked_on=None,
184
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
187
# hand off the request to the smart server
188
client_medium = transport.get_smart_medium()
189
except errors.NoSmartMedium:
192
# Decline to open it if the server doesn't support our required
193
# version (3) so that the VFS-based transport will do it.
194
if client_medium.should_probe():
196
server_version = client_medium.protocol_version()
197
if server_version != '2':
201
except errors.SmartProtocolError:
202
# Apparently there's no usable smart server there, even though
203
# the medium supports the smart protocol.
208
client = _SmartClient(client_medium)
209
path = client.remote_path_from_transport(transport)
210
if client_medium._is_remote_before((1, 16)):
213
# TODO: lookup the local format from a server hint.
214
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
215
self._supply_sub_formats_to(local_dir_format)
216
return local_dir_format.initialize_on_transport_ex(transport,
217
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
218
force_new_repo=force_new_repo, stacked_on=stacked_on,
219
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
220
make_working_trees=make_working_trees, shared_repo=shared_repo,
222
return self._initialize_on_transport_ex_rpc(client, path, transport,
223
use_existing_dir, create_prefix, force_new_repo, stacked_on,
224
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
226
def _initialize_on_transport_ex_rpc(self, client, path, transport,
227
use_existing_dir, create_prefix, force_new_repo, stacked_on,
228
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
230
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
231
args.append(self._serialize_NoneTrueFalse(create_prefix))
232
args.append(self._serialize_NoneTrueFalse(force_new_repo))
233
args.append(self._serialize_NoneString(stacked_on))
234
# stack_on_pwd is often/usually our transport
237
stack_on_pwd = transport.relpath(stack_on_pwd)
240
except errors.PathNotChild:
242
args.append(self._serialize_NoneString(stack_on_pwd))
243
args.append(self._serialize_NoneString(repo_format_name))
244
args.append(self._serialize_NoneTrueFalse(make_working_trees))
245
args.append(self._serialize_NoneTrueFalse(shared_repo))
246
request_network_name = self._network_name or \
247
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
249
response = client.call('BzrDirFormat.initialize_ex_1.16',
250
request_network_name, path, *args)
251
except errors.UnknownSmartMethod:
252
client._medium._remember_remote_is_before((1,16))
253
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
254
self._supply_sub_formats_to(local_dir_format)
255
return local_dir_format.initialize_on_transport_ex(transport,
256
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
257
force_new_repo=force_new_repo, stacked_on=stacked_on,
258
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
259
make_working_trees=make_working_trees, shared_repo=shared_repo,
261
except errors.ErrorFromSmartServer, err:
262
_translate_error(err, path=path)
263
repo_path = response[0]
264
bzrdir_name = response[6]
265
require_stacking = response[7]
266
require_stacking = self.parse_NoneTrueFalse(require_stacking)
267
format = RemoteBzrDirFormat()
268
format._network_name = bzrdir_name
269
self._supply_sub_formats_to(format)
270
bzrdir = RemoteBzrDir(transport, format, _client=client)
272
repo_format = response_tuple_to_repo_format(response[1:])
276
repo_bzrdir_format = RemoteBzrDirFormat()
277
repo_bzrdir_format._network_name = response[5]
278
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
282
final_stack = response[8] or None
283
final_stack_pwd = response[9] or None
285
final_stack_pwd = urlutils.join(
286
transport.base, final_stack_pwd)
287
remote_repo = RemoteRepository(repo_bzr, repo_format)
288
if len(response) > 10:
289
# Updated server verb that locks remotely.
290
repo_lock_token = response[10] or None
291
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
293
remote_repo.dont_leave_lock_in_place()
295
remote_repo.lock_write()
296
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
297
final_stack_pwd, require_stacking)
298
policy.acquire_repository()
302
bzrdir._format.set_branch_format(self.get_branch_format())
304
# The repo has already been created, but we need to make sure that
305
# we'll make a stackable branch.
306
bzrdir._format.require_stacking(_skip_repo=True)
307
return remote_repo, bzrdir, require_stacking, policy
309
def _open(self, transport):
310
return RemoteBzrDir(transport, self)
312
def __eq__(self, other):
313
if not isinstance(other, RemoteBzrDirFormat):
315
return self.get_format_description() == other.get_format_description()
317
def __return_repository_format(self):
318
# Always return a RemoteRepositoryFormat object, but if a specific bzr
319
# repository format has been asked for, tell the RemoteRepositoryFormat
320
# that it should use that for init() etc.
321
result = RemoteRepositoryFormat()
322
custom_format = getattr(self, '_repository_format', None)
324
if isinstance(custom_format, RemoteRepositoryFormat):
327
# We will use the custom format to create repositories over the
328
# wire; expose its details like rich_root_data for code to
330
result._custom_format = custom_format
333
def get_branch_format(self):
334
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
335
if not isinstance(result, RemoteBranchFormat):
336
new_result = RemoteBranchFormat()
337
new_result._custom_format = result
339
self.set_branch_format(new_result)
343
repository_format = property(__return_repository_format,
344
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
347
class RemoteControlStore(config.IniFileStore):
348
"""Control store which attempts to use HPSS calls to retrieve control store.
350
Note that this is specific to bzr-based formats.
353
def __init__(self, bzrdir):
354
super(RemoteControlStore, self).__init__()
356
self._real_store = None
358
def lock_write(self, token=None):
360
return self._real_store.lock_write(token)
364
return self._real_store.unlock()
368
# We need to be able to override the undecorated implementation
369
self.save_without_locking()
371
def save_without_locking(self):
372
super(RemoteControlStore, self).save()
374
def _ensure_real(self):
375
self.bzrdir._ensure_real()
376
if self._real_store is None:
377
self._real_store = config.ControlStore(self.bzrdir)
379
def external_url(self):
380
return self.bzrdir.user_url
382
def _load_content(self):
383
medium = self.bzrdir._client._medium
384
path = self.bzrdir._path_for_remote_call(self.bzrdir._client)
386
response, handler = self.bzrdir._call_expecting_body(
387
'BzrDir.get_config_file', path)
388
except errors.UnknownSmartMethod:
390
return self._real_store._load_content()
391
if len(response) and response[0] != 'ok':
392
raise errors.UnexpectedSmartServerResponse(response)
393
return handler.read_body_bytes()
395
def _save_content(self, content):
396
# FIXME JRV 2011-11-22: Ideally this should use a
397
# HPSS call too, but at the moment it is not possible
398
# to write lock control directories.
400
return self._real_store._save_content(content)
403
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
404
"""Control directory on a remote server, accessed via bzr:// or similar."""
406
def __init__(self, transport, format, _client=None, _force_probe=False):
407
"""Construct a RemoteBzrDir.
409
:param _client: Private parameter for testing. Disables probing and the
410
use of a real bzrdir.
412
_mod_bzrdir.BzrDir.__init__(self, transport, format)
413
# this object holds a delegated bzrdir that uses file-level operations
414
# to talk to the other side
415
self._real_bzrdir = None
416
self._has_working_tree = None
417
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
418
# create_branch for details.
419
self._next_open_branch_result = None
422
medium = transport.get_smart_medium()
423
self._client = client._SmartClient(medium)
425
self._client = _client
432
return '%s(%r)' % (self.__class__.__name__, self._client)
434
def _probe_bzrdir(self):
435
medium = self._client._medium
436
path = self._path_for_remote_call(self._client)
437
if medium._is_remote_before((2, 1)):
441
self._rpc_open_2_1(path)
443
except errors.UnknownSmartMethod:
444
medium._remember_remote_is_before((2, 1))
447
def _rpc_open_2_1(self, path):
448
response = self._call('BzrDir.open_2.1', path)
449
if response == ('no',):
450
raise errors.NotBranchError(path=self.root_transport.base)
451
elif response[0] == 'yes':
452
if response[1] == 'yes':
453
self._has_working_tree = True
454
elif response[1] == 'no':
455
self._has_working_tree = False
457
raise errors.UnexpectedSmartServerResponse(response)
459
raise errors.UnexpectedSmartServerResponse(response)
461
def _rpc_open(self, path):
462
response = self._call('BzrDir.open', path)
463
if response not in [('yes',), ('no',)]:
464
raise errors.UnexpectedSmartServerResponse(response)
465
if response == ('no',):
466
raise errors.NotBranchError(path=self.root_transport.base)
468
def _ensure_real(self):
469
"""Ensure that there is a _real_bzrdir set.
471
Used before calls to self._real_bzrdir.
473
if not self._real_bzrdir:
474
if 'hpssvfs' in debug.debug_flags:
476
warning('VFS BzrDir access triggered\n%s',
477
''.join(traceback.format_stack()))
478
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
479
self.root_transport, _server_formats=False)
480
self._format._network_name = \
481
self._real_bzrdir._format.network_name()
483
def _translate_error(self, err, **context):
484
_translate_error(err, bzrdir=self, **context)
486
def break_lock(self):
487
# Prevent aliasing problems in the next_open_branch_result cache.
488
# See create_branch for rationale.
489
self._next_open_branch_result = None
490
return _mod_bzrdir.BzrDir.break_lock(self)
492
def _vfs_cloning_metadir(self, require_stacking=False):
494
return self._real_bzrdir.cloning_metadir(
495
require_stacking=require_stacking)
497
def cloning_metadir(self, require_stacking=False):
498
medium = self._client._medium
499
if medium._is_remote_before((1, 13)):
500
return self._vfs_cloning_metadir(require_stacking=require_stacking)
501
verb = 'BzrDir.cloning_metadir'
506
path = self._path_for_remote_call(self._client)
508
response = self._call(verb, path, stacking)
509
except errors.UnknownSmartMethod:
510
medium._remember_remote_is_before((1, 13))
511
return self._vfs_cloning_metadir(require_stacking=require_stacking)
512
except errors.UnknownErrorFromSmartServer, err:
513
if err.error_tuple != ('BranchReference',):
515
# We need to resolve the branch reference to determine the
516
# cloning_metadir. This causes unnecessary RPCs to open the
517
# referenced branch (and bzrdir, etc) but only when the caller
518
# didn't already resolve the branch reference.
519
referenced_branch = self.open_branch()
520
return referenced_branch.bzrdir.cloning_metadir()
521
if len(response) != 3:
522
raise errors.UnexpectedSmartServerResponse(response)
523
control_name, repo_name, branch_info = response
524
if len(branch_info) != 2:
525
raise errors.UnexpectedSmartServerResponse(response)
526
branch_ref, branch_name = branch_info
528
format = controldir.network_format_registry.get(control_name)
530
raise errors.UnknownFormatError(kind='control', format=control_name)
534
format.repository_format = _mod_repository.network_format_registry.get(
537
raise errors.UnknownFormatError(kind='repository',
539
if branch_ref == 'ref':
540
# XXX: we need possible_transports here to avoid reopening the
541
# connection to the referenced location
542
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
543
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
544
format.set_branch_format(branch_format)
545
elif branch_ref == 'branch':
548
branch_format = branch.network_format_registry.get(
551
raise errors.UnknownFormatError(kind='branch',
553
format.set_branch_format(branch_format)
555
raise errors.UnexpectedSmartServerResponse(response)
558
def create_repository(self, shared=False):
559
# as per meta1 formats - just delegate to the format object which may
561
result = self._format.repository_format.initialize(self, shared)
562
if not isinstance(result, RemoteRepository):
563
return self.open_repository()
567
def destroy_repository(self):
568
"""See BzrDir.destroy_repository"""
569
path = self._path_for_remote_call(self._client)
571
response = self._call('BzrDir.destroy_repository', path)
572
except errors.UnknownSmartMethod:
574
self._real_bzrdir.destroy_repository()
576
if response[0] != 'ok':
577
raise SmartProtocolError('unexpected response code %s' % (response,))
579
def create_branch(self, name=None, repository=None,
580
append_revisions_only=None):
581
# as per meta1 formats - just delegate to the format object which may
583
real_branch = self._format.get_branch_format().initialize(self,
584
name=name, repository=repository,
585
append_revisions_only=append_revisions_only)
586
if not isinstance(real_branch, RemoteBranch):
587
if not isinstance(repository, RemoteRepository):
588
raise AssertionError(
589
'need a RemoteRepository to use with RemoteBranch, got %r'
591
result = RemoteBranch(self, repository, real_branch, name=name)
594
# BzrDir.clone_on_transport() uses the result of create_branch but does
595
# not return it to its callers; we save approximately 8% of our round
596
# trips by handing the branch we created back to the first caller to
597
# open_branch rather than probing anew. Long term we need a API in
598
# bzrdir that doesn't discard result objects (like result_branch).
600
self._next_open_branch_result = result
603
def destroy_branch(self, name=None):
604
"""See BzrDir.destroy_branch"""
605
path = self._path_for_remote_call(self._client)
611
response = self._call('BzrDir.destroy_branch', path, *args)
612
except errors.UnknownSmartMethod:
614
self._real_bzrdir.destroy_branch(name=name)
615
self._next_open_branch_result = None
617
self._next_open_branch_result = None
618
if response[0] != 'ok':
619
raise SmartProtocolError('unexpected response code %s' % (response,))
621
def create_workingtree(self, revision_id=None, from_branch=None,
622
accelerator_tree=None, hardlink=False):
623
raise errors.NotLocalUrl(self.transport.base)
625
def find_branch_format(self, name=None):
626
"""Find the branch 'format' for this bzrdir.
628
This might be a synthetic object for e.g. RemoteBranch and SVN.
630
b = self.open_branch(name=name)
633
def get_branch_reference(self, name=None):
634
"""See BzrDir.get_branch_reference()."""
636
# XXX JRV20100304: Support opening colocated branches
637
raise errors.NoColocatedBranchSupport(self)
638
response = self._get_branch_reference()
639
if response[0] == 'ref':
644
def _get_branch_reference(self):
645
path = self._path_for_remote_call(self._client)
646
medium = self._client._medium
648
('BzrDir.open_branchV3', (2, 1)),
649
('BzrDir.open_branchV2', (1, 13)),
650
('BzrDir.open_branch', None),
652
for verb, required_version in candidate_calls:
653
if required_version and medium._is_remote_before(required_version):
656
response = self._call(verb, path)
657
except errors.UnknownSmartMethod:
658
if required_version is None:
660
medium._remember_remote_is_before(required_version)
663
if verb == 'BzrDir.open_branch':
664
if response[0] != 'ok':
665
raise errors.UnexpectedSmartServerResponse(response)
666
if response[1] != '':
667
return ('ref', response[1])
669
return ('branch', '')
670
if response[0] not in ('ref', 'branch'):
671
raise errors.UnexpectedSmartServerResponse(response)
674
def _get_tree_branch(self, name=None):
675
"""See BzrDir._get_tree_branch()."""
676
return None, self.open_branch(name=name)
678
def open_branch(self, name=None, unsupported=False,
679
ignore_fallbacks=False, possible_transports=None):
681
raise NotImplementedError('unsupported flag support not implemented yet.')
682
if self._next_open_branch_result is not None:
683
# See create_branch for details.
684
result = self._next_open_branch_result
685
self._next_open_branch_result = None
687
response = self._get_branch_reference()
688
if response[0] == 'ref':
689
# a branch reference, use the existing BranchReference logic.
690
format = BranchReferenceFormat()
691
return format.open(self, name=name, _found=True,
692
location=response[1], ignore_fallbacks=ignore_fallbacks,
693
possible_transports=possible_transports)
694
branch_format_name = response[1]
695
if not branch_format_name:
696
branch_format_name = None
697
format = RemoteBranchFormat(network_name=branch_format_name)
698
return RemoteBranch(self, self.find_repository(), format=format,
699
setup_stacking=not ignore_fallbacks, name=name,
700
possible_transports=possible_transports)
702
def _open_repo_v1(self, path):
703
verb = 'BzrDir.find_repository'
704
response = self._call(verb, path)
705
if response[0] != 'ok':
706
raise errors.UnexpectedSmartServerResponse(response)
707
# servers that only support the v1 method don't support external
710
repo = self._real_bzrdir.open_repository()
711
response = response + ('no', repo._format.network_name())
712
return response, repo
714
def _open_repo_v2(self, path):
715
verb = 'BzrDir.find_repositoryV2'
716
response = self._call(verb, path)
717
if response[0] != 'ok':
718
raise errors.UnexpectedSmartServerResponse(response)
720
repo = self._real_bzrdir.open_repository()
721
response = response + (repo._format.network_name(),)
722
return response, repo
724
def _open_repo_v3(self, path):
725
verb = 'BzrDir.find_repositoryV3'
726
medium = self._client._medium
727
if medium._is_remote_before((1, 13)):
728
raise errors.UnknownSmartMethod(verb)
730
response = self._call(verb, path)
731
except errors.UnknownSmartMethod:
732
medium._remember_remote_is_before((1, 13))
734
if response[0] != 'ok':
735
raise errors.UnexpectedSmartServerResponse(response)
736
return response, None
738
def open_repository(self):
739
path = self._path_for_remote_call(self._client)
741
for probe in [self._open_repo_v3, self._open_repo_v2,
744
response, real_repo = probe(path)
746
except errors.UnknownSmartMethod:
749
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
750
if response[0] != 'ok':
751
raise errors.UnexpectedSmartServerResponse(response)
752
if len(response) != 6:
753
raise SmartProtocolError('incorrect response length %s' % (response,))
754
if response[1] == '':
755
# repo is at this dir.
756
format = response_tuple_to_repo_format(response[2:])
757
# Used to support creating a real format instance when needed.
758
format._creating_bzrdir = self
759
remote_repo = RemoteRepository(self, format)
760
format._creating_repo = remote_repo
761
if real_repo is not None:
762
remote_repo._set_real_repository(real_repo)
765
raise errors.NoRepositoryPresent(self)
767
def has_workingtree(self):
768
if self._has_working_tree is None:
769
path = self._path_for_remote_call(self._client)
771
response = self._call('BzrDir.has_workingtree', path)
772
except errors.UnknownSmartMethod:
774
self._has_working_tree = self._real_bzrdir.has_workingtree()
776
if response[0] not in ('yes', 'no'):
777
raise SmartProtocolError('unexpected response code %s' % (response,))
778
self._has_working_tree = (response[0] == 'yes')
779
return self._has_working_tree
781
def open_workingtree(self, recommend_upgrade=True):
782
if self.has_workingtree():
783
raise errors.NotLocalUrl(self.root_transport)
785
raise errors.NoWorkingTree(self.root_transport.base)
787
def _path_for_remote_call(self, client):
788
"""Return the path to be used for this bzrdir in a remote call."""
789
return urlutils.split_segment_parameters_raw(
790
client.remote_path_from_transport(self.root_transport))[0]
792
def get_branch_transport(self, branch_format, name=None):
794
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
796
def get_repository_transport(self, repository_format):
798
return self._real_bzrdir.get_repository_transport(repository_format)
800
def get_workingtree_transport(self, workingtree_format):
802
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
804
def can_convert_format(self):
805
"""Upgrading of remote bzrdirs is not supported yet."""
808
def needs_format_conversion(self, format):
809
"""Upgrading of remote bzrdirs is not supported yet."""
812
def _get_config(self):
813
return RemoteBzrDirConfig(self)
815
def _get_config_store(self):
816
return RemoteControlStore(self)
819
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
820
"""Format for repositories accessed over a _SmartClient.
822
Instances of this repository are represented by RemoteRepository
825
The RemoteRepositoryFormat is parameterized during construction
826
to reflect the capabilities of the real, remote format. Specifically
827
the attributes rich_root_data and supports_tree_reference are set
828
on a per instance basis, and are not set (and should not be) at
831
:ivar _custom_format: If set, a specific concrete repository format that
832
will be used when initializing a repository with this
833
RemoteRepositoryFormat.
834
:ivar _creating_repo: If set, the repository object that this
835
RemoteRepositoryFormat was created for: it can be called into
836
to obtain data like the network name.
839
_matchingbzrdir = RemoteBzrDirFormat()
840
supports_full_versioned_files = True
841
supports_leaving_lock = True
844
_mod_repository.RepositoryFormat.__init__(self)
845
self._custom_format = None
846
self._network_name = None
847
self._creating_bzrdir = None
848
self._revision_graph_can_have_wrong_parents = None
849
self._supports_chks = None
850
self._supports_external_lookups = None
851
self._supports_tree_reference = None
852
self._supports_funky_characters = None
853
self._supports_nesting_repositories = None
854
self._rich_root_data = None
857
return "%s(_network_name=%r)" % (self.__class__.__name__,
861
def fast_deltas(self):
863
return self._custom_format.fast_deltas
866
def rich_root_data(self):
867
if self._rich_root_data is None:
869
self._rich_root_data = self._custom_format.rich_root_data
870
return self._rich_root_data
873
def supports_chks(self):
874
if self._supports_chks is None:
876
self._supports_chks = self._custom_format.supports_chks
877
return self._supports_chks
880
def supports_external_lookups(self):
881
if self._supports_external_lookups is None:
883
self._supports_external_lookups = \
884
self._custom_format.supports_external_lookups
885
return self._supports_external_lookups
888
def supports_funky_characters(self):
889
if self._supports_funky_characters is None:
891
self._supports_funky_characters = \
892
self._custom_format.supports_funky_characters
893
return self._supports_funky_characters
896
def supports_nesting_repositories(self):
897
if self._supports_nesting_repositories is None:
899
self._supports_nesting_repositories = \
900
self._custom_format.supports_nesting_repositories
901
return self._supports_nesting_repositories
904
def supports_tree_reference(self):
905
if self._supports_tree_reference is None:
907
self._supports_tree_reference = \
908
self._custom_format.supports_tree_reference
909
return self._supports_tree_reference
912
def revision_graph_can_have_wrong_parents(self):
913
if self._revision_graph_can_have_wrong_parents is None:
915
self._revision_graph_can_have_wrong_parents = \
916
self._custom_format.revision_graph_can_have_wrong_parents
917
return self._revision_graph_can_have_wrong_parents
919
def _vfs_initialize(self, a_bzrdir, shared):
920
"""Helper for common code in initialize."""
921
if self._custom_format:
922
# Custom format requested
923
result = self._custom_format.initialize(a_bzrdir, shared=shared)
924
elif self._creating_bzrdir is not None:
925
# Use the format that the repository we were created to back
927
prior_repo = self._creating_bzrdir.open_repository()
928
prior_repo._ensure_real()
929
result = prior_repo._real_repository._format.initialize(
930
a_bzrdir, shared=shared)
932
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
933
# support remote initialization.
934
# We delegate to a real object at this point (as RemoteBzrDir
935
# delegate to the repository format which would lead to infinite
936
# recursion if we just called a_bzrdir.create_repository.
937
a_bzrdir._ensure_real()
938
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
939
if not isinstance(result, RemoteRepository):
940
return self.open(a_bzrdir)
944
def initialize(self, a_bzrdir, shared=False):
945
# Being asked to create on a non RemoteBzrDir:
946
if not isinstance(a_bzrdir, RemoteBzrDir):
947
return self._vfs_initialize(a_bzrdir, shared)
948
medium = a_bzrdir._client._medium
949
if medium._is_remote_before((1, 13)):
950
return self._vfs_initialize(a_bzrdir, shared)
951
# Creating on a remote bzr dir.
952
# 1) get the network name to use.
953
if self._custom_format:
954
network_name = self._custom_format.network_name()
955
elif self._network_name:
956
network_name = self._network_name
958
# Select the current bzrlib default and ask for that.
959
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
960
reference_format = reference_bzrdir_format.repository_format
961
network_name = reference_format.network_name()
962
# 2) try direct creation via RPC
963
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
964
verb = 'BzrDir.create_repository'
970
response = a_bzrdir._call(verb, path, network_name, shared_str)
971
except errors.UnknownSmartMethod:
972
# Fallback - use vfs methods
973
medium._remember_remote_is_before((1, 13))
974
return self._vfs_initialize(a_bzrdir, shared)
976
# Turn the response into a RemoteRepository object.
977
format = response_tuple_to_repo_format(response[1:])
978
# Used to support creating a real format instance when needed.
979
format._creating_bzrdir = a_bzrdir
980
remote_repo = RemoteRepository(a_bzrdir, format)
981
format._creating_repo = remote_repo
984
def open(self, a_bzrdir):
985
if not isinstance(a_bzrdir, RemoteBzrDir):
986
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
987
return a_bzrdir.open_repository()
989
def _ensure_real(self):
990
if self._custom_format is None:
992
self._custom_format = _mod_repository.network_format_registry.get(
995
raise errors.UnknownFormatError(kind='repository',
996
format=self._network_name)
999
def _fetch_order(self):
1001
return self._custom_format._fetch_order
1004
def _fetch_uses_deltas(self):
1006
return self._custom_format._fetch_uses_deltas
1009
def _fetch_reconcile(self):
1011
return self._custom_format._fetch_reconcile
1013
def get_format_description(self):
1015
return 'Remote: ' + self._custom_format.get_format_description()
1017
def __eq__(self, other):
1018
return self.__class__ is other.__class__
1020
def network_name(self):
1021
if self._network_name:
1022
return self._network_name
1023
self._creating_repo._ensure_real()
1024
return self._creating_repo._real_repository._format.network_name()
1027
def pack_compresses(self):
1029
return self._custom_format.pack_compresses
1032
def _serializer(self):
1034
return self._custom_format._serializer
1037
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1038
lock._RelockDebugMixin):
1039
"""Repository accessed over rpc.
1041
For the moment most operations are performed using local transport-backed
1045
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
1046
"""Create a RemoteRepository instance.
1048
:param remote_bzrdir: The bzrdir hosting this repository.
1049
:param format: The RemoteFormat object to use.
1050
:param real_repository: If not None, a local implementation of the
1051
repository logic for the repository, usually accessing the data
1053
:param _client: Private testing parameter - override the smart client
1054
to be used by the repository.
1057
self._real_repository = real_repository
1059
self._real_repository = None
1060
self.bzrdir = remote_bzrdir
1062
self._client = remote_bzrdir._client
1064
self._client = _client
1065
self._format = format
1066
self._lock_mode = None
1067
self._lock_token = None
1068
self._write_group_tokens = None
1069
self._lock_count = 0
1070
self._leave_lock = False
1071
# Cache of revision parents; misses are cached during read locks, and
1072
# write locks when no _real_repository has been set.
1073
self._unstacked_provider = graph.CachingParentsProvider(
1074
get_parent_map=self._get_parent_map_rpc)
1075
self._unstacked_provider.disable_cache()
1077
# These depend on the actual remote format, so force them off for
1078
# maximum compatibility. XXX: In future these should depend on the
1079
# remote repository instance, but this is irrelevant until we perform
1080
# reconcile via an RPC call.
1081
self._reconcile_does_inventory_gc = False
1082
self._reconcile_fixes_text_parents = False
1083
self._reconcile_backsup_inventory = False
1084
self.base = self.bzrdir.transport.base
1085
# Additional places to query for data.
1086
self._fallback_repositories = []
1089
def user_transport(self):
1090
return self.bzrdir.user_transport
1093
def control_transport(self):
1094
# XXX: Normally you shouldn't directly get at the remote repository
1095
# transport, but I'm not sure it's worth making this method
1096
# optional -- mbp 2010-04-21
1097
return self.bzrdir.get_repository_transport(None)
1100
return "%s(%s)" % (self.__class__.__name__, self.base)
1104
def abort_write_group(self, suppress_errors=False):
1105
"""Complete a write group on the decorated repository.
1107
Smart methods perform operations in a single step so this API
1108
is not really applicable except as a compatibility thunk
1109
for older plugins that don't use e.g. the CommitBuilder
1112
:param suppress_errors: see Repository.abort_write_group.
1114
if self._real_repository:
1116
return self._real_repository.abort_write_group(
1117
suppress_errors=suppress_errors)
1118
if not self.is_in_write_group():
1120
mutter('(suppressed) not in write group')
1122
raise errors.BzrError("not in write group")
1123
path = self.bzrdir._path_for_remote_call(self._client)
1125
response = self._call('Repository.abort_write_group', path,
1126
self._lock_token, self._write_group_tokens)
1127
except Exception, exc:
1128
self._write_group = None
1129
if not suppress_errors:
1131
mutter('abort_write_group failed')
1132
log_exception_quietly()
1133
note(gettext('bzr: ERROR (ignored): %s'), exc)
1135
if response != ('ok', ):
1136
raise errors.UnexpectedSmartServerResponse(response)
1137
self._write_group_tokens = None
1140
def chk_bytes(self):
1141
"""Decorate the real repository for now.
1143
In the long term a full blown network facility is needed to avoid
1144
creating a real repository object locally.
1147
return self._real_repository.chk_bytes
1149
def commit_write_group(self):
1150
"""Complete a write group on the decorated repository.
1152
Smart methods perform operations in a single step so this API
1153
is not really applicable except as a compatibility thunk
1154
for older plugins that don't use e.g. the CommitBuilder
1157
if self._real_repository:
1159
return self._real_repository.commit_write_group()
1160
if not self.is_in_write_group():
1161
raise errors.BzrError("not in write group")
1162
path = self.bzrdir._path_for_remote_call(self._client)
1163
response = self._call('Repository.commit_write_group', path,
1164
self._lock_token, self._write_group_tokens)
1165
if response != ('ok', ):
1166
raise errors.UnexpectedSmartServerResponse(response)
1167
self._write_group_tokens = None
1169
def resume_write_group(self, tokens):
1170
if self._real_repository:
1171
return self._real_repository.resume_write_group(tokens)
1172
path = self.bzrdir._path_for_remote_call(self._client)
1174
response = self._call('Repository.check_write_group', path,
1175
self._lock_token, tokens)
1176
except errors.UnknownSmartMethod:
1178
return self._real_repository.resume_write_group(tokens)
1179
if response != ('ok', ):
1180
raise errors.UnexpectedSmartServerResponse(response)
1181
self._write_group_tokens = tokens
1183
def suspend_write_group(self):
1184
if self._real_repository:
1185
return self._real_repository.suspend_write_group()
1186
ret = self._write_group_tokens or []
1187
self._write_group_tokens = None
1190
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1192
return self._real_repository.get_missing_parent_inventories(
1193
check_for_missing_texts=check_for_missing_texts)
1195
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1197
return self._real_repository.get_rev_id_for_revno(
1200
def get_rev_id_for_revno(self, revno, known_pair):
1201
"""See Repository.get_rev_id_for_revno."""
1202
path = self.bzrdir._path_for_remote_call(self._client)
1204
if self._client._medium._is_remote_before((1, 17)):
1205
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1206
response = self._call(
1207
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1208
except errors.UnknownSmartMethod:
1209
self._client._medium._remember_remote_is_before((1, 17))
1210
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1211
if response[0] == 'ok':
1212
return True, response[1]
1213
elif response[0] == 'history-incomplete':
1214
known_pair = response[1:3]
1215
for fallback in self._fallback_repositories:
1216
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1221
# Not found in any fallbacks
1222
return False, known_pair
1224
raise errors.UnexpectedSmartServerResponse(response)
1226
def _ensure_real(self):
1227
"""Ensure that there is a _real_repository set.
1229
Used before calls to self._real_repository.
1231
Note that _ensure_real causes many roundtrips to the server which are
1232
not desirable, and prevents the use of smart one-roundtrip RPC's to
1233
perform complex operations (such as accessing parent data, streaming
1234
revisions etc). Adding calls to _ensure_real should only be done when
1235
bringing up new functionality, adding fallbacks for smart methods that
1236
require a fallback path, and never to replace an existing smart method
1237
invocation. If in doubt chat to the bzr network team.
1239
if self._real_repository is None:
1240
if 'hpssvfs' in debug.debug_flags:
1242
warning('VFS Repository access triggered\n%s',
1243
''.join(traceback.format_stack()))
1244
self._unstacked_provider.missing_keys.clear()
1245
self.bzrdir._ensure_real()
1246
self._set_real_repository(
1247
self.bzrdir._real_bzrdir.open_repository())
1249
def _translate_error(self, err, **context):
1250
self.bzrdir._translate_error(err, repository=self, **context)
1252
def find_text_key_references(self):
1253
"""Find the text key references within the repository.
1255
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1256
to whether they were referred to by the inventory of the
1257
revision_id that they contain. The inventory texts from all present
1258
revision ids are assessed to generate this report.
1261
return self._real_repository.find_text_key_references()
1263
def _generate_text_key_index(self):
1264
"""Generate a new text key index for the repository.
1266
This is an expensive function that will take considerable time to run.
1268
:return: A dict mapping (file_id, revision_id) tuples to a list of
1269
parents, also (file_id, revision_id) tuples.
1272
return self._real_repository._generate_text_key_index()
1274
def _get_revision_graph(self, revision_id):
1275
"""Private method for using with old (< 1.2) servers to fallback."""
1276
if revision_id is None:
1278
elif _mod_revision.is_null(revision_id):
1281
path = self.bzrdir._path_for_remote_call(self._client)
1282
response = self._call_expecting_body(
1283
'Repository.get_revision_graph', path, revision_id)
1284
response_tuple, response_handler = response
1285
if response_tuple[0] != 'ok':
1286
raise errors.UnexpectedSmartServerResponse(response_tuple)
1287
coded = response_handler.read_body_bytes()
1289
# no revisions in this repository!
1291
lines = coded.split('\n')
1294
d = tuple(line.split())
1295
revision_graph[d[0]] = d[1:]
1297
return revision_graph
1299
def _get_sink(self):
1300
"""See Repository._get_sink()."""
1301
return RemoteStreamSink(self)
1303
def _get_source(self, to_format):
1304
"""Return a source for streaming from this repository."""
1305
return RemoteStreamSource(self, to_format)
1308
def get_file_graph(self):
1309
return graph.Graph(self.texts)
1312
def has_revision(self, revision_id):
1313
"""True if this repository has a copy of the revision."""
1314
# Copy of bzrlib.repository.Repository.has_revision
1315
return revision_id in self.has_revisions((revision_id,))
1318
def has_revisions(self, revision_ids):
1319
"""Probe to find out the presence of multiple revisions.
1321
:param revision_ids: An iterable of revision_ids.
1322
:return: A set of the revision_ids that were present.
1324
# Copy of bzrlib.repository.Repository.has_revisions
1325
parent_map = self.get_parent_map(revision_ids)
1326
result = set(parent_map)
1327
if _mod_revision.NULL_REVISION in revision_ids:
1328
result.add(_mod_revision.NULL_REVISION)
1331
def _has_same_fallbacks(self, other_repo):
1332
"""Returns true if the repositories have the same fallbacks."""
1333
# XXX: copied from Repository; it should be unified into a base class
1334
# <https://bugs.launchpad.net/bzr/+bug/401622>
1335
my_fb = self._fallback_repositories
1336
other_fb = other_repo._fallback_repositories
1337
if len(my_fb) != len(other_fb):
1339
for f, g in zip(my_fb, other_fb):
1340
if not f.has_same_location(g):
1344
def has_same_location(self, other):
1345
# TODO: Move to RepositoryBase and unify with the regular Repository
1346
# one; unfortunately the tests rely on slightly different behaviour at
1347
# present -- mbp 20090710
1348
return (self.__class__ is other.__class__ and
1349
self.bzrdir.transport.base == other.bzrdir.transport.base)
1351
def get_graph(self, other_repository=None):
1352
"""Return the graph for this repository format"""
1353
parents_provider = self._make_parents_provider(other_repository)
1354
return graph.Graph(parents_provider)
1357
def get_known_graph_ancestry(self, revision_ids):
1358
"""Return the known graph for a set of revision ids and their ancestors.
1360
st = static_tuple.StaticTuple
1361
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1362
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1363
return graph.GraphThunkIdsToKeys(known_graph)
1365
def gather_stats(self, revid=None, committers=None):
1366
"""See Repository.gather_stats()."""
1367
path = self.bzrdir._path_for_remote_call(self._client)
1368
# revid can be None to indicate no revisions, not just NULL_REVISION
1369
if revid is None or _mod_revision.is_null(revid):
1373
if committers is None or not committers:
1374
fmt_committers = 'no'
1376
fmt_committers = 'yes'
1377
response_tuple, response_handler = self._call_expecting_body(
1378
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1379
if response_tuple[0] != 'ok':
1380
raise errors.UnexpectedSmartServerResponse(response_tuple)
1382
body = response_handler.read_body_bytes()
1384
for line in body.split('\n'):
1387
key, val_text = line.split(':')
1388
if key in ('revisions', 'size', 'committers'):
1389
result[key] = int(val_text)
1390
elif key in ('firstrev', 'latestrev'):
1391
values = val_text.split(' ')[1:]
1392
result[key] = (float(values[0]), long(values[1]))
1396
def find_branches(self, using=False):
1397
"""See Repository.find_branches()."""
1398
# should be an API call to the server.
1400
return self._real_repository.find_branches(using=using)
1402
def get_physical_lock_status(self):
1403
"""See Repository.get_physical_lock_status()."""
1404
path = self.bzrdir._path_for_remote_call(self._client)
1406
response = self._call('Repository.get_physical_lock_status', path)
1407
except errors.UnknownSmartMethod:
1409
return self._real_repository.get_physical_lock_status()
1410
if response[0] not in ('yes', 'no'):
1411
raise errors.UnexpectedSmartServerResponse(response)
1412
return (response[0] == 'yes')
1414
def is_in_write_group(self):
1415
"""Return True if there is an open write group.
1417
write groups are only applicable locally for the smart server..
1419
if self._write_group_tokens is not None:
1421
if self._real_repository:
1422
return self._real_repository.is_in_write_group()
1424
def is_locked(self):
1425
return self._lock_count >= 1
1427
def is_shared(self):
1428
"""See Repository.is_shared()."""
1429
path = self.bzrdir._path_for_remote_call(self._client)
1430
response = self._call('Repository.is_shared', path)
1431
if response[0] not in ('yes', 'no'):
1432
raise SmartProtocolError('unexpected response code %s' % (response,))
1433
return response[0] == 'yes'
1435
def is_write_locked(self):
1436
return self._lock_mode == 'w'
1438
def _warn_if_deprecated(self, branch=None):
1439
# If we have a real repository, the check will be done there, if we
1440
# don't the check will be done remotely.
1443
def lock_read(self):
1444
"""Lock the repository for read operations.
1446
:return: A bzrlib.lock.LogicalLockResult.
1448
# wrong eventually - want a local lock cache context
1449
if not self._lock_mode:
1450
self._note_lock('r')
1451
self._lock_mode = 'r'
1452
self._lock_count = 1
1453
self._unstacked_provider.enable_cache(cache_misses=True)
1454
if self._real_repository is not None:
1455
self._real_repository.lock_read()
1456
for repo in self._fallback_repositories:
1459
self._lock_count += 1
1460
return lock.LogicalLockResult(self.unlock)
1462
def _remote_lock_write(self, token):
1463
path = self.bzrdir._path_for_remote_call(self._client)
1466
err_context = {'token': token}
1467
response = self._call('Repository.lock_write', path, token,
1469
if response[0] == 'ok':
1470
ok, token = response
1473
raise errors.UnexpectedSmartServerResponse(response)
1475
def lock_write(self, token=None, _skip_rpc=False):
1476
if not self._lock_mode:
1477
self._note_lock('w')
1479
if self._lock_token is not None:
1480
if token != self._lock_token:
1481
raise errors.TokenMismatch(token, self._lock_token)
1482
self._lock_token = token
1484
self._lock_token = self._remote_lock_write(token)
1485
# if self._lock_token is None, then this is something like packs or
1486
# svn where we don't get to lock the repo, or a weave style repository
1487
# where we cannot lock it over the wire and attempts to do so will
1489
if self._real_repository is not None:
1490
self._real_repository.lock_write(token=self._lock_token)
1491
if token is not None:
1492
self._leave_lock = True
1494
self._leave_lock = False
1495
self._lock_mode = 'w'
1496
self._lock_count = 1
1497
cache_misses = self._real_repository is None
1498
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1499
for repo in self._fallback_repositories:
1500
# Writes don't affect fallback repos
1502
elif self._lock_mode == 'r':
1503
raise errors.ReadOnlyError(self)
1505
self._lock_count += 1
1506
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1508
def leave_lock_in_place(self):
1509
if not self._lock_token:
1510
raise NotImplementedError(self.leave_lock_in_place)
1511
self._leave_lock = True
1513
def dont_leave_lock_in_place(self):
1514
if not self._lock_token:
1515
raise NotImplementedError(self.dont_leave_lock_in_place)
1516
self._leave_lock = False
1518
def _set_real_repository(self, repository):
1519
"""Set the _real_repository for this repository.
1521
:param repository: The repository to fallback to for non-hpss
1522
implemented operations.
1524
if self._real_repository is not None:
1525
# Replacing an already set real repository.
1526
# We cannot do this [currently] if the repository is locked -
1527
# synchronised state might be lost.
1528
if self.is_locked():
1529
raise AssertionError('_real_repository is already set')
1530
if isinstance(repository, RemoteRepository):
1531
raise AssertionError()
1532
self._real_repository = repository
1533
# three code paths happen here:
1534
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1535
# up stacking. In this case self._fallback_repositories is [], and the
1536
# real repo is already setup. Preserve the real repo and
1537
# RemoteRepository.add_fallback_repository will avoid adding
1539
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1540
# ensure_real is triggered from a branch, the real repository to
1541
# set already has a matching list with separate instances, but
1542
# as they are also RemoteRepositories we don't worry about making the
1543
# lists be identical.
1544
# 3) new servers, RemoteRepository.ensure_real is triggered before
1545
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1546
# and need to populate it.
1547
if (self._fallback_repositories and
1548
len(self._real_repository._fallback_repositories) !=
1549
len(self._fallback_repositories)):
1550
if len(self._real_repository._fallback_repositories):
1551
raise AssertionError(
1552
"cannot cleanly remove existing _fallback_repositories")
1553
for fb in self._fallback_repositories:
1554
self._real_repository.add_fallback_repository(fb)
1555
if self._lock_mode == 'w':
1556
# if we are already locked, the real repository must be able to
1557
# acquire the lock with our token.
1558
self._real_repository.lock_write(self._lock_token)
1559
elif self._lock_mode == 'r':
1560
self._real_repository.lock_read()
1561
if self._write_group_tokens is not None:
1562
# if we are already in a write group, resume it
1563
self._real_repository.resume_write_group(self._write_group_tokens)
1564
self._write_group_tokens = None
1566
def start_write_group(self):
1567
"""Start a write group on the decorated repository.
1569
Smart methods perform operations in a single step so this API
1570
is not really applicable except as a compatibility thunk
1571
for older plugins that don't use e.g. the CommitBuilder
1574
if self._real_repository:
1576
return self._real_repository.start_write_group()
1577
if not self.is_write_locked():
1578
raise errors.NotWriteLocked(self)
1579
if self._write_group_tokens is not None:
1580
raise errors.BzrError('already in a write group')
1581
path = self.bzrdir._path_for_remote_call(self._client)
1583
response = self._call('Repository.start_write_group', path,
1585
except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1587
return self._real_repository.start_write_group()
1588
if response[0] != 'ok':
1589
raise errors.UnexpectedSmartServerResponse(response)
1590
self._write_group_tokens = response[1]
1592
def _unlock(self, token):
1593
path = self.bzrdir._path_for_remote_call(self._client)
1595
# with no token the remote repository is not persistently locked.
1597
err_context = {'token': token}
1598
response = self._call('Repository.unlock', path, token,
1600
if response == ('ok',):
1603
raise errors.UnexpectedSmartServerResponse(response)
1605
@only_raises(errors.LockNotHeld, errors.LockBroken)
1607
if not self._lock_count:
1608
return lock.cant_unlock_not_held(self)
1609
self._lock_count -= 1
1610
if self._lock_count > 0:
1612
self._unstacked_provider.disable_cache()
1613
old_mode = self._lock_mode
1614
self._lock_mode = None
1616
# The real repository is responsible at present for raising an
1617
# exception if it's in an unfinished write group. However, it
1618
# normally will *not* actually remove the lock from disk - that's
1619
# done by the server on receiving the Repository.unlock call.
1620
# This is just to let the _real_repository stay up to date.
1621
if self._real_repository is not None:
1622
self._real_repository.unlock()
1623
elif self._write_group_tokens is not None:
1624
self.abort_write_group()
1626
# The rpc-level lock should be released even if there was a
1627
# problem releasing the vfs-based lock.
1629
# Only write-locked repositories need to make a remote method
1630
# call to perform the unlock.
1631
old_token = self._lock_token
1632
self._lock_token = None
1633
if not self._leave_lock:
1634
self._unlock(old_token)
1635
# Fallbacks are always 'lock_read()' so we don't pay attention to
1637
for repo in self._fallback_repositories:
1640
def break_lock(self):
1641
# should hand off to the network
1642
path = self.bzrdir._path_for_remote_call(self._client)
1644
response = self._call("Repository.break_lock", path)
1645
except errors.UnknownSmartMethod:
1647
return self._real_repository.break_lock()
1648
if response != ('ok',):
1649
raise errors.UnexpectedSmartServerResponse(response)
1651
def _get_tarball(self, compression):
1652
"""Return a TemporaryFile containing a repository tarball.
1654
Returns None if the server does not support sending tarballs.
1657
path = self.bzrdir._path_for_remote_call(self._client)
1659
response, protocol = self._call_expecting_body(
1660
'Repository.tarball', path, compression)
1661
except errors.UnknownSmartMethod:
1662
protocol.cancel_read_body()
1664
if response[0] == 'ok':
1665
# Extract the tarball and return it
1666
t = tempfile.NamedTemporaryFile()
1667
# TODO: rpc layer should read directly into it...
1668
t.write(protocol.read_body_bytes())
1671
raise errors.UnexpectedSmartServerResponse(response)
1674
def sprout(self, to_bzrdir, revision_id=None):
1675
"""Create a descendent repository for new development.
1677
Unlike clone, this does not copy the settings of the repository.
1679
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1680
dest_repo.fetch(self, revision_id=revision_id)
1683
def _create_sprouting_repo(self, a_bzrdir, shared):
1684
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
1685
# use target default format.
1686
dest_repo = a_bzrdir.create_repository()
1688
# Most control formats need the repository to be specifically
1689
# created, but on some old all-in-one formats it's not needed
1691
dest_repo = self._format.initialize(a_bzrdir, shared=shared)
1692
except errors.UninitializableFormat:
1693
dest_repo = a_bzrdir.open_repository()
1696
### These methods are just thin shims to the VFS object for now.
1699
def revision_tree(self, revision_id):
1700
revision_id = _mod_revision.ensure_null(revision_id)
1701
if revision_id == _mod_revision.NULL_REVISION:
1702
return InventoryRevisionTree(self,
1703
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1705
return list(self.revision_trees([revision_id]))[0]
1707
def get_serializer_format(self):
1708
path = self.bzrdir._path_for_remote_call(self._client)
1710
response = self._call('VersionedFileRepository.get_serializer_format',
1712
except errors.UnknownSmartMethod:
1714
return self._real_repository.get_serializer_format()
1715
if response[0] != 'ok':
1716
raise errors.UnexpectedSmartServerResponse(response)
1719
def get_commit_builder(self, branch, parents, config, timestamp=None,
1720
timezone=None, committer=None, revprops=None,
1721
revision_id=None, lossy=False):
1722
# FIXME: It ought to be possible to call this without immediately
1723
# triggering _ensure_real. For now it's the easiest thing to do.
1725
real_repo = self._real_repository
1726
builder = real_repo.get_commit_builder(branch, parents,
1727
config, timestamp=timestamp, timezone=timezone,
1728
committer=committer, revprops=revprops,
1729
revision_id=revision_id, lossy=lossy)
1732
def add_fallback_repository(self, repository):
1733
"""Add a repository to use for looking up data not held locally.
1735
:param repository: A repository.
1737
if not self._format.supports_external_lookups:
1738
raise errors.UnstackableRepositoryFormat(
1739
self._format.network_name(), self.base)
1740
# We need to accumulate additional repositories here, to pass them in
1743
# Make the check before we lock: this raises an exception.
1744
self._check_fallback_repository(repository)
1745
if self.is_locked():
1746
# We will call fallback.unlock() when we transition to the unlocked
1747
# state, so always add a lock here. If a caller passes us a locked
1748
# repository, they are responsible for unlocking it later.
1749
repository.lock_read()
1750
self._fallback_repositories.append(repository)
1751
# If self._real_repository was parameterised already (e.g. because a
1752
# _real_branch had its get_stacked_on_url method called), then the
1753
# repository to be added may already be in the _real_repositories list.
1754
if self._real_repository is not None:
1755
fallback_locations = [repo.user_url for repo in
1756
self._real_repository._fallback_repositories]
1757
if repository.user_url not in fallback_locations:
1758
self._real_repository.add_fallback_repository(repository)
1760
def _check_fallback_repository(self, repository):
1761
"""Check that this repository can fallback to repository safely.
1763
Raise an error if not.
1765
:param repository: A repository to fallback to.
1767
return _mod_repository.InterRepository._assert_same_model(
1770
def add_inventory(self, revid, inv, parents):
1772
return self._real_repository.add_inventory(revid, inv, parents)
1774
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1775
parents, basis_inv=None, propagate_caches=False):
1777
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1778
delta, new_revision_id, parents, basis_inv=basis_inv,
1779
propagate_caches=propagate_caches)
1781
def add_revision(self, rev_id, rev, inv=None, config=None):
1783
return self._real_repository.add_revision(
1784
rev_id, rev, inv=inv, config=config)
1787
def get_inventory(self, revision_id):
1788
return list(self.iter_inventories([revision_id]))[0]
1790
def iter_inventories(self, revision_ids, ordering=None):
1792
return self._real_repository.iter_inventories(revision_ids, ordering)
1795
def get_revision(self, revision_id):
1796
return self.get_revisions([revision_id])[0]
1798
def get_transaction(self):
1800
return self._real_repository.get_transaction()
1803
def clone(self, a_bzrdir, revision_id=None):
1804
dest_repo = self._create_sprouting_repo(
1805
a_bzrdir, shared=self.is_shared())
1806
self.copy_content_into(dest_repo, revision_id)
1809
def make_working_trees(self):
1810
"""See Repository.make_working_trees"""
1811
path = self.bzrdir._path_for_remote_call(self._client)
1813
response = self._call('Repository.make_working_trees', path)
1814
except errors.UnknownSmartMethod:
1816
return self._real_repository.make_working_trees()
1817
if response[0] not in ('yes', 'no'):
1818
raise SmartProtocolError('unexpected response code %s' % (response,))
1819
return response[0] == 'yes'
1821
def refresh_data(self):
1822
"""Re-read any data needed to synchronise with disk.
1824
This method is intended to be called after another repository instance
1825
(such as one used by a smart server) has inserted data into the
1826
repository. On all repositories this will work outside of write groups.
1827
Some repository formats (pack and newer for bzrlib native formats)
1828
support refresh_data inside write groups. If called inside a write
1829
group on a repository that does not support refreshing in a write group
1830
IsInWriteGroupError will be raised.
1832
if self._real_repository is not None:
1833
self._real_repository.refresh_data()
1835
def revision_ids_to_search_result(self, result_set):
1836
"""Convert a set of revision ids to a graph SearchResult."""
1837
result_parents = set()
1838
for parents in self.get_graph().get_parent_map(
1839
result_set).itervalues():
1840
result_parents.update(parents)
1841
included_keys = result_set.intersection(result_parents)
1842
start_keys = result_set.difference(included_keys)
1843
exclude_keys = result_parents.difference(result_set)
1844
result = graph.SearchResult(start_keys, exclude_keys,
1845
len(result_set), result_set)
1849
def search_missing_revision_ids(self, other,
1850
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1851
find_ghosts=True, revision_ids=None, if_present_ids=None,
1853
"""Return the revision ids that other has that this does not.
1855
These are returned in topological order.
1857
revision_id: only return revision ids included by revision_id.
1859
if symbol_versioning.deprecated_passed(revision_id):
1860
symbol_versioning.warn(
1861
'search_missing_revision_ids(revision_id=...) was '
1862
'deprecated in 2.4. Use revision_ids=[...] instead.',
1863
DeprecationWarning, stacklevel=2)
1864
if revision_ids is not None:
1865
raise AssertionError(
1866
'revision_ids is mutually exclusive with revision_id')
1867
if revision_id is not None:
1868
revision_ids = [revision_id]
1869
inter_repo = _mod_repository.InterRepository.get(other, self)
1870
return inter_repo.search_missing_revision_ids(
1871
find_ghosts=find_ghosts, revision_ids=revision_ids,
1872
if_present_ids=if_present_ids, limit=limit)
1874
def fetch(self, source, revision_id=None, find_ghosts=False,
1876
# No base implementation to use as RemoteRepository is not a subclass
1877
# of Repository; so this is a copy of Repository.fetch().
1878
if fetch_spec is not None and revision_id is not None:
1879
raise AssertionError(
1880
"fetch_spec and revision_id are mutually exclusive.")
1881
if self.is_in_write_group():
1882
raise errors.InternalBzrError(
1883
"May not fetch while in a write group.")
1884
# fast path same-url fetch operations
1885
if (self.has_same_location(source)
1886
and fetch_spec is None
1887
and self._has_same_fallbacks(source)):
1888
# check that last_revision is in 'from' and then return a
1890
if (revision_id is not None and
1891
not _mod_revision.is_null(revision_id)):
1892
self.get_revision(revision_id)
1894
# if there is no specific appropriate InterRepository, this will get
1895
# the InterRepository base class, which raises an
1896
# IncompatibleRepositories when asked to fetch.
1897
inter = _mod_repository.InterRepository.get(source, self)
1898
return inter.fetch(revision_id=revision_id,
1899
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1901
def create_bundle(self, target, base, fileobj, format=None):
1903
self._real_repository.create_bundle(target, base, fileobj, format)
1906
@symbol_versioning.deprecated_method(
1907
symbol_versioning.deprecated_in((2, 4, 0)))
1908
def get_ancestry(self, revision_id, topo_sorted=True):
1910
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1912
def fileids_altered_by_revision_ids(self, revision_ids):
1914
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1916
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1918
return self._real_repository._get_versioned_file_checker(
1919
revisions, revision_versions_cache)
1921
def _iter_files_bytes_rpc(self, desired_files, absent):
1922
path = self.bzrdir._path_for_remote_call(self._client)
1925
for (file_id, revid, identifier) in desired_files:
1926
lines.append("%s\0%s" % (
1927
osutils.safe_file_id(file_id),
1928
osutils.safe_revision_id(revid)))
1929
identifiers.append(identifier)
1930
(response_tuple, response_handler) = (
1931
self._call_with_body_bytes_expecting_body(
1932
"Repository.iter_files_bytes", (path, ), "\n".join(lines)))
1933
if response_tuple != ('ok', ):
1934
response_handler.cancel_read_body()
1935
raise errors.UnexpectedSmartServerResponse(response_tuple)
1936
byte_stream = response_handler.read_streamed_body()
1937
def decompress_stream(start, byte_stream, unused):
1938
decompressor = zlib.decompressobj()
1939
yield decompressor.decompress(start)
1940
while decompressor.unused_data == "":
1942
data = byte_stream.next()
1943
except StopIteration:
1945
yield decompressor.decompress(data)
1946
yield decompressor.flush()
1947
unused.append(decompressor.unused_data)
1950
while not "\n" in unused:
1951
unused += byte_stream.next()
1952
header, rest = unused.split("\n", 1)
1953
args = header.split("\0")
1954
if args[0] == "absent":
1955
absent[identifiers[int(args[3])]] = (args[1], args[2])
1958
elif args[0] == "ok":
1961
raise errors.UnexpectedSmartServerResponse(args)
1963
yield (identifiers[idx],
1964
decompress_stream(rest, byte_stream, unused_chunks))
1965
unused = "".join(unused_chunks)
1967
def iter_files_bytes(self, desired_files):
1968
"""See Repository.iter_file_bytes.
1972
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
1973
desired_files, absent):
1974
yield identifier, bytes_iterator
1975
for fallback in self._fallback_repositories:
1978
desired_files = [(key[0], key[1], identifier) for
1979
(identifier, key) in absent.iteritems()]
1980
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
1981
del absent[identifier]
1982
yield identifier, bytes_iterator
1984
# There may be more missing items, but raise an exception
1986
missing_identifier = absent.keys()[0]
1987
missing_key = absent[missing_identifier]
1988
raise errors.RevisionNotPresent(revision_id=missing_key[1],
1989
file_id=missing_key[0])
1990
except errors.UnknownSmartMethod:
1992
for (identifier, bytes_iterator) in (
1993
self._real_repository.iter_files_bytes(desired_files)):
1994
yield identifier, bytes_iterator
1996
def get_cached_parent_map(self, revision_ids):
1997
"""See bzrlib.CachingParentsProvider.get_cached_parent_map"""
1998
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2000
def get_parent_map(self, revision_ids):
2001
"""See bzrlib.Graph.get_parent_map()."""
2002
return self._make_parents_provider().get_parent_map(revision_ids)
2004
def _get_parent_map_rpc(self, keys):
2005
"""Helper for get_parent_map that performs the RPC."""
2006
medium = self._client._medium
2007
if medium._is_remote_before((1, 2)):
2008
# We already found out that the server can't understand
2009
# Repository.get_parent_map requests, so just fetch the whole
2012
# Note that this reads the whole graph, when only some keys are
2013
# wanted. On this old server there's no way (?) to get them all
2014
# in one go, and the user probably will have seen a warning about
2015
# the server being old anyhow.
2016
rg = self._get_revision_graph(None)
2017
# There is an API discrepancy between get_parent_map and
2018
# get_revision_graph. Specifically, a "key:()" pair in
2019
# get_revision_graph just means a node has no parents. For
2020
# "get_parent_map" it means the node is a ghost. So fix up the
2021
# graph to correct this.
2022
# https://bugs.launchpad.net/bzr/+bug/214894
2023
# There is one other "bug" which is that ghosts in
2024
# get_revision_graph() are not returned at all. But we won't worry
2025
# about that for now.
2026
for node_id, parent_ids in rg.iteritems():
2027
if parent_ids == ():
2028
rg[node_id] = (NULL_REVISION,)
2029
rg[NULL_REVISION] = ()
2034
raise ValueError('get_parent_map(None) is not valid')
2035
if NULL_REVISION in keys:
2036
keys.discard(NULL_REVISION)
2037
found_parents = {NULL_REVISION:()}
2039
return found_parents
2042
# TODO(Needs analysis): We could assume that the keys being requested
2043
# from get_parent_map are in a breadth first search, so typically they
2044
# will all be depth N from some common parent, and we don't have to
2045
# have the server iterate from the root parent, but rather from the
2046
# keys we're searching; and just tell the server the keyspace we
2047
# already have; but this may be more traffic again.
2049
# Transform self._parents_map into a search request recipe.
2050
# TODO: Manage this incrementally to avoid covering the same path
2051
# repeatedly. (The server will have to on each request, but the less
2052
# work done the better).
2054
# Negative caching notes:
2055
# new server sends missing when a request including the revid
2056
# 'include-missing:' is present in the request.
2057
# missing keys are serialised as missing:X, and we then call
2058
# provider.note_missing(X) for-all X
2059
parents_map = self._unstacked_provider.get_cached_map()
2060
if parents_map is None:
2061
# Repository is not locked, so there's no cache.
2063
if _DEFAULT_SEARCH_DEPTH <= 0:
2064
(start_set, stop_keys,
2065
key_count) = graph.search_result_from_parent_map(
2066
parents_map, self._unstacked_provider.missing_keys)
2068
(start_set, stop_keys,
2069
key_count) = graph.limited_search_result_from_parent_map(
2070
parents_map, self._unstacked_provider.missing_keys,
2071
keys, depth=_DEFAULT_SEARCH_DEPTH)
2072
recipe = ('manual', start_set, stop_keys, key_count)
2073
body = self._serialise_search_recipe(recipe)
2074
path = self.bzrdir._path_for_remote_call(self._client)
2076
if type(key) is not str:
2078
"key %r not a plain string" % (key,))
2079
verb = 'Repository.get_parent_map'
2080
args = (path, 'include-missing:') + tuple(keys)
2082
response = self._call_with_body_bytes_expecting_body(
2084
except errors.UnknownSmartMethod:
2085
# Server does not support this method, so get the whole graph.
2086
# Worse, we have to force a disconnection, because the server now
2087
# doesn't realise it has a body on the wire to consume, so the
2088
# only way to recover is to abandon the connection.
2090
'Server is too old for fast get_parent_map, reconnecting. '
2091
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2093
# To avoid having to disconnect repeatedly, we keep track of the
2094
# fact the server doesn't understand remote methods added in 1.2.
2095
medium._remember_remote_is_before((1, 2))
2096
# Recurse just once and we should use the fallback code.
2097
return self._get_parent_map_rpc(keys)
2098
response_tuple, response_handler = response
2099
if response_tuple[0] not in ['ok']:
2100
response_handler.cancel_read_body()
2101
raise errors.UnexpectedSmartServerResponse(response_tuple)
2102
if response_tuple[0] == 'ok':
2103
coded = bz2.decompress(response_handler.read_body_bytes())
2105
# no revisions found
2107
lines = coded.split('\n')
2110
d = tuple(line.split())
2112
revision_graph[d[0]] = d[1:]
2115
if d[0].startswith('missing:'):
2117
self._unstacked_provider.note_missing_key(revid)
2119
# no parents - so give the Graph result
2121
revision_graph[d[0]] = (NULL_REVISION,)
2122
return revision_graph
2125
def get_signature_text(self, revision_id):
2126
path = self.bzrdir._path_for_remote_call(self._client)
2128
response_tuple, response_handler = self._call_expecting_body(
2129
'Repository.get_revision_signature_text', path, revision_id)
2130
except errors.UnknownSmartMethod:
2132
return self._real_repository.get_signature_text(revision_id)
2133
except errors.NoSuchRevision, err:
2134
for fallback in self._fallback_repositories:
2136
return fallback.get_signature_text(revision_id)
2137
except errors.NoSuchRevision:
2141
if response_tuple[0] != 'ok':
2142
raise errors.UnexpectedSmartServerResponse(response_tuple)
2143
return response_handler.read_body_bytes()
2146
def _get_inventory_xml(self, revision_id):
2148
return self._real_repository._get_inventory_xml(revision_id)
2150
def reconcile(self, other=None, thorough=False):
2152
return self._real_repository.reconcile(other=other, thorough=thorough)
2154
def all_revision_ids(self):
2155
path = self.bzrdir._path_for_remote_call(self._client)
2157
response_tuple, response_handler = self._call_expecting_body(
2158
"Repository.all_revision_ids", path)
2159
except errors.UnknownSmartMethod:
2161
return self._real_repository.all_revision_ids()
2162
if response_tuple != ("ok", ):
2163
raise errors.UnexpectedSmartServerResponse(response_tuple)
2164
revids = set(response_handler.read_body_bytes().splitlines())
2165
for fallback in self._fallback_repositories:
2166
revids.update(set(fallback.all_revision_ids()))
2170
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2172
return self._real_repository.get_deltas_for_revisions(revisions,
2173
specific_fileids=specific_fileids)
2176
def get_revision_delta(self, revision_id, specific_fileids=None):
2177
r = self.get_revision(revision_id)
2178
return list(self.get_deltas_for_revisions([r],
2179
specific_fileids=specific_fileids))[0]
2182
def revision_trees(self, revision_ids):
2183
inventories = self.iter_inventories(revision_ids)
2184
for inv in inventories:
2185
yield InventoryRevisionTree(self, inv, inv.revision_id)
2188
def get_revision_reconcile(self, revision_id):
2190
return self._real_repository.get_revision_reconcile(revision_id)
2193
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2195
return self._real_repository.check(revision_ids=revision_ids,
2196
callback_refs=callback_refs, check_repo=check_repo)
2198
def copy_content_into(self, destination, revision_id=None):
2199
"""Make a complete copy of the content in self into destination.
2201
This is a destructive operation! Do not use it on existing
2204
interrepo = _mod_repository.InterRepository.get(self, destination)
2205
return interrepo.copy_content(revision_id)
2207
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2208
# get a tarball of the remote repository, and copy from that into the
2211
# TODO: Maybe a progress bar while streaming the tarball?
2212
note(gettext("Copying repository content as tarball..."))
2213
tar_file = self._get_tarball('bz2')
2214
if tar_file is None:
2216
destination = to_bzrdir.create_repository()
2218
tar = tarfile.open('repository', fileobj=tar_file,
2220
tmpdir = osutils.mkdtemp()
2222
_extract_tar(tar, tmpdir)
2223
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
2224
tmp_repo = tmp_bzrdir.open_repository()
2225
tmp_repo.copy_content_into(destination, revision_id)
2227
osutils.rmtree(tmpdir)
2231
# TODO: Suggestion from john: using external tar is much faster than
2232
# python's tarfile library, but it may not work on windows.
2235
def inventories(self):
2236
"""Decorate the real repository for now.
2238
In the long term a full blown network facility is needed to
2239
avoid creating a real repository object locally.
2242
return self._real_repository.inventories
2245
def pack(self, hint=None, clean_obsolete_packs=False):
2246
"""Compress the data within the repository.
2251
body = "".join([l+"\n" for l in hint])
2252
path = self.bzrdir._path_for_remote_call(self._client)
2254
response, handler = self._call_with_body_bytes_expecting_body(
2255
'Repository.pack', (path, self._lock_token,
2256
str(clean_obsolete_packs)), body)
2257
except errors.UnknownSmartMethod:
2259
return self._real_repository.pack(hint=hint,
2260
clean_obsolete_packs=clean_obsolete_packs)
2261
handler.cancel_read_body()
2262
if response != ('ok', ):
2263
raise errors.UnexpectedSmartServerResponse(response)
2266
def revisions(self):
2267
"""Decorate the real repository for now.
2269
In the long term a full blown network facility is needed.
2272
return self._real_repository.revisions
2274
def set_make_working_trees(self, new_value):
2276
new_value_str = "True"
2278
new_value_str = "False"
2279
path = self.bzrdir._path_for_remote_call(self._client)
2281
response = self._call(
2282
'Repository.set_make_working_trees', path, new_value_str)
2283
except errors.UnknownSmartMethod:
2285
self._real_repository.set_make_working_trees(new_value)
2287
if response[0] != 'ok':
2288
raise errors.UnexpectedSmartServerResponse(response)
2291
def signatures(self):
2292
"""Decorate the real repository for now.
2294
In the long term a full blown network facility is needed to avoid
2295
creating a real repository object locally.
2298
return self._real_repository.signatures
2301
def sign_revision(self, revision_id, gpg_strategy):
2302
testament = _mod_testament.Testament.from_revision(self, revision_id)
2303
plaintext = testament.as_short_text()
2304
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2308
"""Decorate the real repository for now.
2310
In the long term a full blown network facility is needed to avoid
2311
creating a real repository object locally.
2314
return self._real_repository.texts
2316
def _iter_revisions_rpc(self, revision_ids):
2317
body = "\n".join(revision_ids)
2318
path = self.bzrdir._path_for_remote_call(self._client)
2319
response_tuple, response_handler = (
2320
self._call_with_body_bytes_expecting_body(
2321
"Repository.iter_revisions", (path, ), body))
2322
if response_tuple[0] != "ok":
2323
raise errors.UnexpectedSmartServerResponse(response_tuple)
2324
serializer_format = response_tuple[1]
2325
serializer = serializer_format_registry.get(serializer_format)
2326
byte_stream = response_handler.read_streamed_body()
2327
decompressor = zlib.decompressobj()
2329
for bytes in byte_stream:
2330
chunks.append(decompressor.decompress(bytes))
2331
if decompressor.unused_data != "":
2332
chunks.append(decompressor.flush())
2333
yield serializer.read_revision_from_string("".join(chunks))
2334
unused = decompressor.unused_data
2335
decompressor = zlib.decompressobj()
2336
chunks = [decompressor.decompress(unused)]
2337
chunks.append(decompressor.flush())
2338
text = "".join(chunks)
2340
yield serializer.read_revision_from_string("".join(chunks))
2343
def get_revisions(self, revision_ids):
2344
if revision_ids is None:
2345
revision_ids = self.all_revision_ids()
2347
for rev_id in revision_ids:
2348
if not rev_id or not isinstance(rev_id, basestring):
2349
raise errors.InvalidRevisionId(
2350
revision_id=rev_id, branch=self)
2352
missing = set(revision_ids)
2354
for rev in self._iter_revisions_rpc(revision_ids):
2355
missing.remove(rev.revision_id)
2356
revs[rev.revision_id] = rev
2357
except errors.UnknownSmartMethod:
2359
return self._real_repository.get_revisions(revision_ids)
2360
for fallback in self._fallback_repositories:
2363
for revid in list(missing):
2364
# XXX JRV 2011-11-20: It would be nice if there was a
2365
# public method on Repository that could be used to query
2366
# for revision objects *without* failing completely if one
2367
# was missing. There is VersionedFileRepository._iter_revisions,
2368
# but unfortunately that's private and not provided by
2369
# all repository implementations.
2371
revs[revid] = fallback.get_revision(revid)
2372
except errors.NoSuchRevision:
2375
missing.remove(revid)
2377
raise errors.NoSuchRevision(self, list(missing)[0])
2378
return [revs[revid] for revid in revision_ids]
2380
def supports_rich_root(self):
2381
return self._format.rich_root_data
2383
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2384
def iter_reverse_revision_history(self, revision_id):
2386
return self._real_repository.iter_reverse_revision_history(revision_id)
2389
def _serializer(self):
2390
return self._format._serializer
2393
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2394
signature = gpg_strategy.sign(plaintext)
2395
self.add_signature_text(revision_id, signature)
2397
def add_signature_text(self, revision_id, signature):
2398
if self._real_repository:
2399
# If there is a real repository the write group will
2400
# be in the real repository as well, so use that:
2402
return self._real_repository.add_signature_text(
2403
revision_id, signature)
2404
path = self.bzrdir._path_for_remote_call(self._client)
2405
response, handler = self._call_with_body_bytes_expecting_body(
2406
'Repository.add_signature_text', (path, self._lock_token,
2407
revision_id) + tuple(self._write_group_tokens), signature)
2408
handler.cancel_read_body()
2410
if response[0] != 'ok':
2411
raise errors.UnexpectedSmartServerResponse(response)
2412
self._write_group_tokens = response[1:]
2414
def has_signature_for_revision_id(self, revision_id):
2415
path = self.bzrdir._path_for_remote_call(self._client)
2417
response = self._call('Repository.has_signature_for_revision_id',
2419
except errors.UnknownSmartMethod:
2421
return self._real_repository.has_signature_for_revision_id(
2423
if response[0] not in ('yes', 'no'):
2424
raise SmartProtocolError('unexpected response code %s' % (response,))
2425
if response[0] == 'yes':
2427
for fallback in self._fallback_repositories:
2428
if fallback.has_signature_for_revision_id(revision_id):
2433
def verify_revision_signature(self, revision_id, gpg_strategy):
2434
if not self.has_signature_for_revision_id(revision_id):
2435
return gpg.SIGNATURE_NOT_SIGNED, None
2436
signature = self.get_signature_text(revision_id)
2438
testament = _mod_testament.Testament.from_revision(self, revision_id)
2439
plaintext = testament.as_short_text()
2441
return gpg_strategy.verify(signature, plaintext)
2443
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2445
return self._real_repository.item_keys_introduced_by(revision_ids,
2446
_files_pb=_files_pb)
2448
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2450
return self._real_repository._find_inconsistent_revision_parents(
2453
def _check_for_inconsistent_revision_parents(self):
2455
return self._real_repository._check_for_inconsistent_revision_parents()
2457
def _make_parents_provider(self, other=None):
2458
providers = [self._unstacked_provider]
2459
if other is not None:
2460
providers.insert(0, other)
2461
return graph.StackedParentsProvider(_LazyListJoin(
2462
providers, self._fallback_repositories))
2464
def _serialise_search_recipe(self, recipe):
2465
"""Serialise a graph search recipe.
2467
:param recipe: A search recipe (start, stop, count).
2468
:return: Serialised bytes.
2470
start_keys = ' '.join(recipe[1])
2471
stop_keys = ' '.join(recipe[2])
2472
count = str(recipe[3])
2473
return '\n'.join((start_keys, stop_keys, count))
2475
def _serialise_search_result(self, search_result):
2476
parts = search_result.get_network_struct()
2477
return '\n'.join(parts)
2480
path = self.bzrdir._path_for_remote_call(self._client)
2482
response = self._call('PackRepository.autopack', path)
2483
except errors.UnknownSmartMethod:
2485
self._real_repository._pack_collection.autopack()
2488
if response[0] != 'ok':
2489
raise errors.UnexpectedSmartServerResponse(response)
2492
class RemoteStreamSink(vf_repository.StreamSink):
2494
def _insert_real(self, stream, src_format, resume_tokens):
2495
self.target_repo._ensure_real()
2496
sink = self.target_repo._real_repository._get_sink()
2497
result = sink.insert_stream(stream, src_format, resume_tokens)
2499
self.target_repo.autopack()
2502
def insert_stream(self, stream, src_format, resume_tokens):
2503
target = self.target_repo
2504
target._unstacked_provider.missing_keys.clear()
2505
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2506
if target._lock_token:
2507
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2508
lock_args = (target._lock_token or '',)
2510
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2512
client = target._client
2513
medium = client._medium
2514
path = target.bzrdir._path_for_remote_call(client)
2515
# Probe for the verb to use with an empty stream before sending the
2516
# real stream to it. We do this both to avoid the risk of sending a
2517
# large request that is then rejected, and because we don't want to
2518
# implement a way to buffer, rewind, or restart the stream.
2520
for verb, required_version in candidate_calls:
2521
if medium._is_remote_before(required_version):
2524
# We've already done the probing (and set _is_remote_before) on
2525
# a previous insert.
2528
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2530
response = client.call_with_body_stream(
2531
(verb, path, '') + lock_args, byte_stream)
2532
except errors.UnknownSmartMethod:
2533
medium._remember_remote_is_before(required_version)
2539
return self._insert_real(stream, src_format, resume_tokens)
2540
self._last_inv_record = None
2541
self._last_substream = None
2542
if required_version < (1, 19):
2543
# Remote side doesn't support inventory deltas. Wrap the stream to
2544
# make sure we don't send any. If the stream contains inventory
2545
# deltas we'll interrupt the smart insert_stream request and
2547
stream = self._stop_stream_if_inventory_delta(stream)
2548
byte_stream = smart_repo._stream_to_byte_stream(
2550
resume_tokens = ' '.join(resume_tokens)
2551
response = client.call_with_body_stream(
2552
(verb, path, resume_tokens) + lock_args, byte_stream)
2553
if response[0][0] not in ('ok', 'missing-basis'):
2554
raise errors.UnexpectedSmartServerResponse(response)
2555
if self._last_substream is not None:
2556
# The stream included an inventory-delta record, but the remote
2557
# side isn't new enough to support them. So we need to send the
2558
# rest of the stream via VFS.
2559
self.target_repo.refresh_data()
2560
return self._resume_stream_with_vfs(response, src_format)
2561
if response[0][0] == 'missing-basis':
2562
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2563
resume_tokens = tokens
2564
return resume_tokens, set(missing_keys)
2566
self.target_repo.refresh_data()
2569
def _resume_stream_with_vfs(self, response, src_format):
2570
"""Resume sending a stream via VFS, first resending the record and
2571
substream that couldn't be sent via an insert_stream verb.
2573
if response[0][0] == 'missing-basis':
2574
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2575
# Ignore missing_keys, we haven't finished inserting yet
2578
def resume_substream():
2579
# Yield the substream that was interrupted.
2580
for record in self._last_substream:
2582
self._last_substream = None
2583
def resume_stream():
2584
# Finish sending the interrupted substream
2585
yield ('inventory-deltas', resume_substream())
2586
# Then simply continue sending the rest of the stream.
2587
for substream_kind, substream in self._last_stream:
2588
yield substream_kind, substream
2589
return self._insert_real(resume_stream(), src_format, tokens)
2591
def _stop_stream_if_inventory_delta(self, stream):
2592
"""Normally this just lets the original stream pass-through unchanged.
2594
However if any 'inventory-deltas' substream occurs it will stop
2595
streaming, and store the interrupted substream and stream in
2596
self._last_substream and self._last_stream so that the stream can be
2597
resumed by _resume_stream_with_vfs.
2600
stream_iter = iter(stream)
2601
for substream_kind, substream in stream_iter:
2602
if substream_kind == 'inventory-deltas':
2603
self._last_substream = substream
2604
self._last_stream = stream_iter
2607
yield substream_kind, substream
2610
class RemoteStreamSource(vf_repository.StreamSource):
2611
"""Stream data from a remote server."""
2613
def get_stream(self, search):
2614
if (self.from_repository._fallback_repositories and
2615
self.to_format._fetch_order == 'topological'):
2616
return self._real_stream(self.from_repository, search)
2619
repos = [self.from_repository]
2625
repos.extend(repo._fallback_repositories)
2626
sources.append(repo)
2627
return self.missing_parents_chain(search, sources)
2629
def get_stream_for_missing_keys(self, missing_keys):
2630
self.from_repository._ensure_real()
2631
real_repo = self.from_repository._real_repository
2632
real_source = real_repo._get_source(self.to_format)
2633
return real_source.get_stream_for_missing_keys(missing_keys)
2635
def _real_stream(self, repo, search):
2636
"""Get a stream for search from repo.
2638
This never called RemoteStreamSource.get_stream, and is a helper
2639
for RemoteStreamSource._get_stream to allow getting a stream
2640
reliably whether fallback back because of old servers or trying
2641
to stream from a non-RemoteRepository (which the stacked support
2644
source = repo._get_source(self.to_format)
2645
if isinstance(source, RemoteStreamSource):
2647
source = repo._real_repository._get_source(self.to_format)
2648
return source.get_stream(search)
2650
def _get_stream(self, repo, search):
2651
"""Core worker to get a stream from repo for search.
2653
This is used by both get_stream and the stacking support logic. It
2654
deliberately gets a stream for repo which does not need to be
2655
self.from_repository. In the event that repo is not Remote, or
2656
cannot do a smart stream, a fallback is made to the generic
2657
repository._get_stream() interface, via self._real_stream.
2659
In the event of stacking, streams from _get_stream will not
2660
contain all the data for search - this is normal (see get_stream).
2662
:param repo: A repository.
2663
:param search: A search.
2665
# Fallbacks may be non-smart
2666
if not isinstance(repo, RemoteRepository):
2667
return self._real_stream(repo, search)
2668
client = repo._client
2669
medium = client._medium
2670
path = repo.bzrdir._path_for_remote_call(client)
2671
search_bytes = repo._serialise_search_result(search)
2672
args = (path, self.to_format.network_name())
2674
('Repository.get_stream_1.19', (1, 19)),
2675
('Repository.get_stream', (1, 13))]
2678
for verb, version in candidate_verbs:
2679
if medium._is_remote_before(version):
2682
response = repo._call_with_body_bytes_expecting_body(
2683
verb, args, search_bytes)
2684
except errors.UnknownSmartMethod:
2685
medium._remember_remote_is_before(version)
2686
except errors.UnknownErrorFromSmartServer, e:
2687
if isinstance(search, graph.EverythingResult):
2688
error_verb = e.error_from_smart_server.error_verb
2689
if error_verb == 'BadSearch':
2690
# Pre-2.4 servers don't support this sort of search.
2691
# XXX: perhaps falling back to VFS on BadSearch is a
2692
# good idea in general? It might provide a little bit
2693
# of protection against client-side bugs.
2694
medium._remember_remote_is_before((2, 4))
2698
response_tuple, response_handler = response
2702
return self._real_stream(repo, search)
2703
if response_tuple[0] != 'ok':
2704
raise errors.UnexpectedSmartServerResponse(response_tuple)
2705
byte_stream = response_handler.read_streamed_body()
2706
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2707
self._record_counter)
2708
if src_format.network_name() != repo._format.network_name():
2709
raise AssertionError(
2710
"Mismatched RemoteRepository and stream src %r, %r" % (
2711
src_format.network_name(), repo._format.network_name()))
2714
def missing_parents_chain(self, search, sources):
2715
"""Chain multiple streams together to handle stacking.
2717
:param search: The overall search to satisfy with streams.
2718
:param sources: A list of Repository objects to query.
2720
self.from_serialiser = self.from_repository._format._serializer
2721
self.seen_revs = set()
2722
self.referenced_revs = set()
2723
# If there are heads in the search, or the key count is > 0, we are not
2725
while not search.is_empty() and len(sources) > 1:
2726
source = sources.pop(0)
2727
stream = self._get_stream(source, search)
2728
for kind, substream in stream:
2729
if kind != 'revisions':
2730
yield kind, substream
2732
yield kind, self.missing_parents_rev_handler(substream)
2733
search = search.refine(self.seen_revs, self.referenced_revs)
2734
self.seen_revs = set()
2735
self.referenced_revs = set()
2736
if not search.is_empty():
2737
for kind, stream in self._get_stream(sources[0], search):
2740
def missing_parents_rev_handler(self, substream):
2741
for content in substream:
2742
revision_bytes = content.get_bytes_as('fulltext')
2743
revision = self.from_serialiser.read_revision_from_string(
2745
self.seen_revs.add(content.key[-1])
2746
self.referenced_revs.update(revision.parent_ids)
2750
class RemoteBranchLockableFiles(LockableFiles):
2751
"""A 'LockableFiles' implementation that talks to a smart server.
2753
This is not a public interface class.
2756
def __init__(self, bzrdir, _client):
2757
self.bzrdir = bzrdir
2758
self._client = _client
2759
self._need_find_modes = True
2760
LockableFiles.__init__(
2761
self, bzrdir.get_branch_transport(None),
2762
'lock', lockdir.LockDir)
2764
def _find_modes(self):
2765
# RemoteBranches don't let the client set the mode of control files.
2766
self._dir_mode = None
2767
self._file_mode = None
2770
class RemoteBranchFormat(branch.BranchFormat):
2772
def __init__(self, network_name=None):
2773
super(RemoteBranchFormat, self).__init__()
2774
self._matchingbzrdir = RemoteBzrDirFormat()
2775
self._matchingbzrdir.set_branch_format(self)
2776
self._custom_format = None
2777
self._network_name = network_name
2779
def __eq__(self, other):
2780
return (isinstance(other, RemoteBranchFormat) and
2781
self.__dict__ == other.__dict__)
2783
def _ensure_real(self):
2784
if self._custom_format is None:
2786
self._custom_format = branch.network_format_registry.get(
2789
raise errors.UnknownFormatError(kind='branch',
2790
format=self._network_name)
2792
def get_format_description(self):
2794
return 'Remote: ' + self._custom_format.get_format_description()
2796
def network_name(self):
2797
return self._network_name
2799
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2800
return a_bzrdir.open_branch(name=name,
2801
ignore_fallbacks=ignore_fallbacks)
2803
def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
2804
# Initialisation when using a local bzrdir object, or a non-vfs init
2805
# method is not available on the server.
2806
# self._custom_format is always set - the start of initialize ensures
2808
if isinstance(a_bzrdir, RemoteBzrDir):
2809
a_bzrdir._ensure_real()
2810
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2811
name, append_revisions_only=append_revisions_only)
2813
# We assume the bzrdir is parameterised; it may not be.
2814
result = self._custom_format.initialize(a_bzrdir, name,
2815
append_revisions_only=append_revisions_only)
2816
if (isinstance(a_bzrdir, RemoteBzrDir) and
2817
not isinstance(result, RemoteBranch)):
2818
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2822
def initialize(self, a_bzrdir, name=None, repository=None,
2823
append_revisions_only=None):
2824
# 1) get the network name to use.
2825
if self._custom_format:
2826
network_name = self._custom_format.network_name()
2828
# Select the current bzrlib default and ask for that.
2829
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
2830
reference_format = reference_bzrdir_format.get_branch_format()
2831
self._custom_format = reference_format
2832
network_name = reference_format.network_name()
2833
# Being asked to create on a non RemoteBzrDir:
2834
if not isinstance(a_bzrdir, RemoteBzrDir):
2835
return self._vfs_initialize(a_bzrdir, name=name,
2836
append_revisions_only=append_revisions_only)
2837
medium = a_bzrdir._client._medium
2838
if medium._is_remote_before((1, 13)):
2839
return self._vfs_initialize(a_bzrdir, name=name,
2840
append_revisions_only=append_revisions_only)
2841
# Creating on a remote bzr dir.
2842
# 2) try direct creation via RPC
2843
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2844
if name is not None:
2845
# XXX JRV20100304: Support creating colocated branches
2846
raise errors.NoColocatedBranchSupport(self)
2847
verb = 'BzrDir.create_branch'
2849
response = a_bzrdir._call(verb, path, network_name)
2850
except errors.UnknownSmartMethod:
2851
# Fallback - use vfs methods
2852
medium._remember_remote_is_before((1, 13))
2853
return self._vfs_initialize(a_bzrdir, name=name,
2854
append_revisions_only=append_revisions_only)
2855
if response[0] != 'ok':
2856
raise errors.UnexpectedSmartServerResponse(response)
2857
# Turn the response into a RemoteRepository object.
2858
format = RemoteBranchFormat(network_name=response[1])
2859
repo_format = response_tuple_to_repo_format(response[3:])
2860
repo_path = response[2]
2861
if repository is not None:
2862
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
2863
url_diff = urlutils.relative_url(repository.user_url,
2866
raise AssertionError(
2867
'repository.user_url %r does not match URL from server '
2868
'response (%r + %r)'
2869
% (repository.user_url, a_bzrdir.user_url, repo_path))
2870
remote_repo = repository
2873
repo_bzrdir = a_bzrdir
2875
repo_bzrdir = RemoteBzrDir(
2876
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
2878
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2879
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2880
format=format, setup_stacking=False, name=name)
2881
if append_revisions_only:
2882
remote_branch.set_append_revisions_only(append_revisions_only)
2883
# XXX: We know this is a new branch, so it must have revno 0, revid
2884
# NULL_REVISION. Creating the branch locked would make this be unable
2885
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2886
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2887
return remote_branch
2889
def make_tags(self, branch):
2891
return self._custom_format.make_tags(branch)
2893
def supports_tags(self):
2894
# Remote branches might support tags, but we won't know until we
2895
# access the real remote branch.
2897
return self._custom_format.supports_tags()
2899
def supports_stacking(self):
2901
return self._custom_format.supports_stacking()
2903
def supports_set_append_revisions_only(self):
2905
return self._custom_format.supports_set_append_revisions_only()
2907
def _use_default_local_heads_to_fetch(self):
2908
# If the branch format is a metadir format *and* its heads_to_fetch
2909
# implementation is not overridden vs the base class, we can use the
2910
# base class logic rather than use the heads_to_fetch RPC. This is
2911
# usually cheaper in terms of net round trips, as the last-revision and
2912
# tags info fetched is cached and would be fetched anyway.
2914
if isinstance(self._custom_format, branch.BranchFormatMetadir):
2915
branch_class = self._custom_format._branch_class()
2916
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
2917
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
2922
class RemoteBranchStore(config.IniFileStore):
2923
"""Branch store which attempts to use HPSS calls to retrieve branch store.
2925
Note that this is specific to bzr-based formats.
2928
def __init__(self, branch):
2929
super(RemoteBranchStore, self).__init__()
2930
self.branch = branch
2932
self._real_store = None
2934
def lock_write(self, token=None):
2935
return self.branch.lock_write(token)
2938
return self.branch.unlock()
2942
# We need to be able to override the undecorated implementation
2943
self.save_without_locking()
2945
def save_without_locking(self):
2946
super(RemoteBranchStore, self).save()
2948
def external_url(self):
2949
return self.branch.user_url
2951
def _load_content(self):
2952
path = self.branch._remote_path()
2954
response, handler = self.branch._call_expecting_body(
2955
'Branch.get_config_file', path)
2956
except errors.UnknownSmartMethod:
2958
return self._real_store._load_content()
2959
if len(response) and response[0] != 'ok':
2960
raise errors.UnexpectedSmartServerResponse(response)
2961
return handler.read_body_bytes()
2963
def _save_content(self, content):
2964
path = self.branch._remote_path()
2966
response, handler = self.branch._call_with_body_bytes_expecting_body(
2967
'Branch.put_config_file', (path,
2968
self.branch._lock_token, self.branch._repo_lock_token),
2970
except errors.UnknownSmartMethod:
2972
return self._real_store._save_content(content)
2973
handler.cancel_read_body()
2974
if response != ('ok', ):
2975
raise errors.UnexpectedSmartServerResponse(response)
2977
def _ensure_real(self):
2978
self.branch._ensure_real()
2979
if self._real_store is None:
2980
self._real_store = config.BranchStore(self.branch)
2983
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2984
"""Branch stored on a server accessed by HPSS RPC.
2986
At the moment most operations are mapped down to simple file operations.
2989
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2990
_client=None, format=None, setup_stacking=True, name=None,
2991
possible_transports=None):
2992
"""Create a RemoteBranch instance.
2994
:param real_branch: An optional local implementation of the branch
2995
format, usually accessing the data via the VFS.
2996
:param _client: Private parameter for testing.
2997
:param format: A RemoteBranchFormat object, None to create one
2998
automatically. If supplied it should have a network_name already
3000
:param setup_stacking: If True make an RPC call to determine the
3001
stacked (or not) status of the branch. If False assume the branch
3003
:param name: Colocated branch name
3005
# We intentionally don't call the parent class's __init__, because it
3006
# will try to assign to self.tags, which is a property in this subclass.
3007
# And the parent's __init__ doesn't do much anyway.
3008
self.bzrdir = remote_bzrdir
3009
if _client is not None:
3010
self._client = _client
3012
self._client = remote_bzrdir._client
3013
self.repository = remote_repository
3014
if real_branch is not None:
3015
self._real_branch = real_branch
3016
# Give the remote repository the matching real repo.
3017
real_repo = self._real_branch.repository
3018
if isinstance(real_repo, RemoteRepository):
3019
real_repo._ensure_real()
3020
real_repo = real_repo._real_repository
3021
self.repository._set_real_repository(real_repo)
3022
# Give the branch the remote repository to let fast-pathing happen.
3023
self._real_branch.repository = self.repository
3025
self._real_branch = None
3026
# Fill out expected attributes of branch for bzrlib API users.
3027
self._clear_cached_state()
3028
# TODO: deprecate self.base in favor of user_url
3029
self.base = self.bzrdir.user_url
3031
self._control_files = None
3032
self._lock_mode = None
3033
self._lock_token = None
3034
self._repo_lock_token = None
3035
self._lock_count = 0
3036
self._leave_lock = False
3037
# Setup a format: note that we cannot call _ensure_real until all the
3038
# attributes above are set: This code cannot be moved higher up in this
3041
self._format = RemoteBranchFormat()
3042
if real_branch is not None:
3043
self._format._network_name = \
3044
self._real_branch._format.network_name()
3046
self._format = format
3047
# when we do _ensure_real we may need to pass ignore_fallbacks to the
3048
# branch.open_branch method.
3049
self._real_ignore_fallbacks = not setup_stacking
3050
if not self._format._network_name:
3051
# Did not get from open_branchV2 - old server.
3053
self._format._network_name = \
3054
self._real_branch._format.network_name()
3055
self.tags = self._format.make_tags(self)
3056
# The base class init is not called, so we duplicate this:
3057
hooks = branch.Branch.hooks['open']
3060
self._is_stacked = False
3062
self._setup_stacking(possible_transports)
3064
def _setup_stacking(self, possible_transports):
3065
# configure stacking into the remote repository, by reading it from
3068
fallback_url = self.get_stacked_on_url()
3069
except (errors.NotStacked, errors.UnstackableBranchFormat,
3070
errors.UnstackableRepositoryFormat), e:
3072
self._is_stacked = True
3073
if possible_transports is None:
3074
possible_transports = []
3076
possible_transports = list(possible_transports)
3077
possible_transports.append(self.bzrdir.root_transport)
3078
self._activate_fallback_location(fallback_url,
3079
possible_transports=possible_transports)
3081
def _get_config(self):
3082
return RemoteBranchConfig(self)
3084
def _get_config_store(self):
3085
return RemoteBranchStore(self)
3087
def _get_real_transport(self):
3088
# if we try vfs access, return the real branch's vfs transport
3090
return self._real_branch._transport
3092
_transport = property(_get_real_transport)
3095
return "%s(%s)" % (self.__class__.__name__, self.base)
3099
def _ensure_real(self):
3100
"""Ensure that there is a _real_branch set.
3102
Used before calls to self._real_branch.
3104
if self._real_branch is None:
3105
if not vfs.vfs_enabled():
3106
raise AssertionError('smart server vfs must be enabled '
3107
'to use vfs implementation')
3108
self.bzrdir._ensure_real()
3109
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3110
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3111
if self.repository._real_repository is None:
3112
# Give the remote repository the matching real repo.
3113
real_repo = self._real_branch.repository
3114
if isinstance(real_repo, RemoteRepository):
3115
real_repo._ensure_real()
3116
real_repo = real_repo._real_repository
3117
self.repository._set_real_repository(real_repo)
3118
# Give the real branch the remote repository to let fast-pathing
3120
self._real_branch.repository = self.repository
3121
if self._lock_mode == 'r':
3122
self._real_branch.lock_read()
3123
elif self._lock_mode == 'w':
3124
self._real_branch.lock_write(token=self._lock_token)
3126
def _translate_error(self, err, **context):
3127
self.repository._translate_error(err, branch=self, **context)
3129
def _clear_cached_state(self):
3130
super(RemoteBranch, self)._clear_cached_state()
3131
if self._real_branch is not None:
3132
self._real_branch._clear_cached_state()
3134
def _clear_cached_state_of_remote_branch_only(self):
3135
"""Like _clear_cached_state, but doesn't clear the cache of
3138
This is useful when falling back to calling a method of
3139
self._real_branch that changes state. In that case the underlying
3140
branch changes, so we need to invalidate this RemoteBranch's cache of
3141
it. However, there's no need to invalidate the _real_branch's cache
3142
too, in fact doing so might harm performance.
3144
super(RemoteBranch, self)._clear_cached_state()
3147
def control_files(self):
3148
# Defer actually creating RemoteBranchLockableFiles until its needed,
3149
# because it triggers an _ensure_real that we otherwise might not need.
3150
if self._control_files is None:
3151
self._control_files = RemoteBranchLockableFiles(
3152
self.bzrdir, self._client)
3153
return self._control_files
3155
def _get_checkout_format(self, lightweight=False):
3158
format = RemoteBzrDirFormat()
3159
self.bzrdir._format._supply_sub_formats_to(format)
3160
format.workingtree_format = self._real_branch._get_checkout_format(
3161
lightweight=lightweight).workingtree_format
3164
return self._real_branch._get_checkout_format(lightweight=False)
3166
def get_physical_lock_status(self):
3167
"""See Branch.get_physical_lock_status()."""
3169
response = self._client.call('Branch.get_physical_lock_status',
3170
self._remote_path())
3171
except errors.UnknownSmartMethod:
3173
return self._real_branch.get_physical_lock_status()
3174
if response[0] not in ('yes', 'no'):
3175
raise errors.UnexpectedSmartServerResponse(response)
3176
return (response[0] == 'yes')
3178
def get_stacked_on_url(self):
3179
"""Get the URL this branch is stacked against.
3181
:raises NotStacked: If the branch is not stacked.
3182
:raises UnstackableBranchFormat: If the branch does not support
3184
:raises UnstackableRepositoryFormat: If the repository does not support
3188
# there may not be a repository yet, so we can't use
3189
# self._translate_error, so we can't use self._call either.
3190
response = self._client.call('Branch.get_stacked_on_url',
3191
self._remote_path())
3192
except errors.ErrorFromSmartServer, err:
3193
# there may not be a repository yet, so we can't call through
3194
# its _translate_error
3195
_translate_error(err, branch=self)
3196
except errors.UnknownSmartMethod, err:
3198
return self._real_branch.get_stacked_on_url()
3199
if response[0] != 'ok':
3200
raise errors.UnexpectedSmartServerResponse(response)
3203
def set_stacked_on_url(self, url):
3204
branch.Branch.set_stacked_on_url(self, url)
3206
self._is_stacked = False
3208
self._is_stacked = True
3210
def _vfs_get_tags_bytes(self):
3212
return self._real_branch._get_tags_bytes()
3215
def _get_tags_bytes(self):
3216
if self._tags_bytes is None:
3217
self._tags_bytes = self._get_tags_bytes_via_hpss()
3218
return self._tags_bytes
3220
def _get_tags_bytes_via_hpss(self):
3221
medium = self._client._medium
3222
if medium._is_remote_before((1, 13)):
3223
return self._vfs_get_tags_bytes()
3225
response = self._call('Branch.get_tags_bytes', self._remote_path())
3226
except errors.UnknownSmartMethod:
3227
medium._remember_remote_is_before((1, 13))
3228
return self._vfs_get_tags_bytes()
3231
def _vfs_set_tags_bytes(self, bytes):
3233
return self._real_branch._set_tags_bytes(bytes)
3235
def _set_tags_bytes(self, bytes):
3236
if self.is_locked():
3237
self._tags_bytes = bytes
3238
medium = self._client._medium
3239
if medium._is_remote_before((1, 18)):
3240
self._vfs_set_tags_bytes(bytes)
3244
self._remote_path(), self._lock_token, self._repo_lock_token)
3245
response = self._call_with_body_bytes(
3246
'Branch.set_tags_bytes', args, bytes)
3247
except errors.UnknownSmartMethod:
3248
medium._remember_remote_is_before((1, 18))
3249
self._vfs_set_tags_bytes(bytes)
3251
def lock_read(self):
3252
"""Lock the branch for read operations.
3254
:return: A bzrlib.lock.LogicalLockResult.
3256
self.repository.lock_read()
3257
if not self._lock_mode:
3258
self._note_lock('r')
3259
self._lock_mode = 'r'
3260
self._lock_count = 1
3261
if self._real_branch is not None:
3262
self._real_branch.lock_read()
3264
self._lock_count += 1
3265
return lock.LogicalLockResult(self.unlock)
3267
def _remote_lock_write(self, token):
3269
branch_token = repo_token = ''
3271
branch_token = token
3272
repo_token = self.repository.lock_write().repository_token
3273
self.repository.unlock()
3274
err_context = {'token': token}
3276
response = self._call(
3277
'Branch.lock_write', self._remote_path(), branch_token,
3278
repo_token or '', **err_context)
3279
except errors.LockContention, e:
3280
# The LockContention from the server doesn't have any
3281
# information about the lock_url. We re-raise LockContention
3282
# with valid lock_url.
3283
raise errors.LockContention('(remote lock)',
3284
self.repository.base.split('.bzr/')[0])
3285
if response[0] != 'ok':
3286
raise errors.UnexpectedSmartServerResponse(response)
3287
ok, branch_token, repo_token = response
3288
return branch_token, repo_token
3290
def lock_write(self, token=None):
3291
if not self._lock_mode:
3292
self._note_lock('w')
3293
# Lock the branch and repo in one remote call.
3294
remote_tokens = self._remote_lock_write(token)
3295
self._lock_token, self._repo_lock_token = remote_tokens
3296
if not self._lock_token:
3297
raise SmartProtocolError('Remote server did not return a token!')
3298
# Tell the self.repository object that it is locked.
3299
self.repository.lock_write(
3300
self._repo_lock_token, _skip_rpc=True)
3302
if self._real_branch is not None:
3303
self._real_branch.lock_write(token=self._lock_token)
3304
if token is not None:
3305
self._leave_lock = True
3307
self._leave_lock = False
3308
self._lock_mode = 'w'
3309
self._lock_count = 1
3310
elif self._lock_mode == 'r':
3311
raise errors.ReadOnlyError(self)
3313
if token is not None:
3314
# A token was given to lock_write, and we're relocking, so
3315
# check that the given token actually matches the one we
3317
if token != self._lock_token:
3318
raise errors.TokenMismatch(token, self._lock_token)
3319
self._lock_count += 1
3320
# Re-lock the repository too.
3321
self.repository.lock_write(self._repo_lock_token)
3322
return BranchWriteLockResult(self.unlock, self._lock_token or None)
3324
def _unlock(self, branch_token, repo_token):
3325
err_context = {'token': str((branch_token, repo_token))}
3326
response = self._call(
3327
'Branch.unlock', self._remote_path(), branch_token,
3328
repo_token or '', **err_context)
3329
if response == ('ok',):
3331
raise errors.UnexpectedSmartServerResponse(response)
3333
@only_raises(errors.LockNotHeld, errors.LockBroken)
3336
self._lock_count -= 1
3337
if not self._lock_count:
3338
self._clear_cached_state()
3339
mode = self._lock_mode
3340
self._lock_mode = None
3341
if self._real_branch is not None:
3342
if (not self._leave_lock and mode == 'w' and
3343
self._repo_lock_token):
3344
# If this RemoteBranch will remove the physical lock
3345
# for the repository, make sure the _real_branch
3346
# doesn't do it first. (Because the _real_branch's
3347
# repository is set to be the RemoteRepository.)
3348
self._real_branch.repository.leave_lock_in_place()
3349
self._real_branch.unlock()
3351
# Only write-locked branched need to make a remote method
3352
# call to perform the unlock.
3354
if not self._lock_token:
3355
raise AssertionError('Locked, but no token!')
3356
branch_token = self._lock_token
3357
repo_token = self._repo_lock_token
3358
self._lock_token = None
3359
self._repo_lock_token = None
3360
if not self._leave_lock:
3361
self._unlock(branch_token, repo_token)
3363
self.repository.unlock()
3365
def break_lock(self):
3367
response = self._call(
3368
'Branch.break_lock', self._remote_path())
3369
except errors.UnknownSmartMethod:
3371
return self._real_branch.break_lock()
3372
if response != ('ok',):
3373
raise errors.UnexpectedSmartServerResponse(response)
3375
def leave_lock_in_place(self):
3376
if not self._lock_token:
3377
raise NotImplementedError(self.leave_lock_in_place)
3378
self._leave_lock = True
3380
def dont_leave_lock_in_place(self):
3381
if not self._lock_token:
3382
raise NotImplementedError(self.dont_leave_lock_in_place)
3383
self._leave_lock = False
3386
def get_rev_id(self, revno, history=None):
3388
return _mod_revision.NULL_REVISION
3389
last_revision_info = self.last_revision_info()
3390
ok, result = self.repository.get_rev_id_for_revno(
3391
revno, last_revision_info)
3394
missing_parent = result[1]
3395
# Either the revision named by the server is missing, or its parent
3396
# is. Call get_parent_map to determine which, so that we report a
3398
parent_map = self.repository.get_parent_map([missing_parent])
3399
if missing_parent in parent_map:
3400
missing_parent = parent_map[missing_parent]
3401
raise errors.RevisionNotPresent(missing_parent, self.repository)
3403
def _read_last_revision_info(self):
3404
response = self._call('Branch.last_revision_info', self._remote_path())
3405
if response[0] != 'ok':
3406
raise SmartProtocolError('unexpected response code %s' % (response,))
3407
revno = int(response[1])
3408
last_revision = response[2]
3409
return (revno, last_revision)
3411
def _gen_revision_history(self):
3412
"""See Branch._gen_revision_history()."""
3413
if self._is_stacked:
3415
return self._real_branch._gen_revision_history()
3416
response_tuple, response_handler = self._call_expecting_body(
3417
'Branch.revision_history', self._remote_path())
3418
if response_tuple[0] != 'ok':
3419
raise errors.UnexpectedSmartServerResponse(response_tuple)
3420
result = response_handler.read_body_bytes().split('\x00')
3425
def _remote_path(self):
3426
return self.bzrdir._path_for_remote_call(self._client)
3428
def _set_last_revision_descendant(self, revision_id, other_branch,
3429
allow_diverged=False, allow_overwrite_descendant=False):
3430
# This performs additional work to meet the hook contract; while its
3431
# undesirable, we have to synthesise the revno to call the hook, and
3432
# not calling the hook is worse as it means changes can't be prevented.
3433
# Having calculated this though, we can't just call into
3434
# set_last_revision_info as a simple call, because there is a set_rh
3435
# hook that some folk may still be using.
3436
old_revno, old_revid = self.last_revision_info()
3437
history = self._lefthand_history(revision_id)
3438
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3439
err_context = {'other_branch': other_branch}
3440
response = self._call('Branch.set_last_revision_ex',
3441
self._remote_path(), self._lock_token, self._repo_lock_token,
3442
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
3444
self._clear_cached_state()
3445
if len(response) != 3 and response[0] != 'ok':
3446
raise errors.UnexpectedSmartServerResponse(response)
3447
new_revno, new_revision_id = response[1:]
3448
self._last_revision_info_cache = new_revno, new_revision_id
3449
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3450
if self._real_branch is not None:
3451
cache = new_revno, new_revision_id
3452
self._real_branch._last_revision_info_cache = cache
3454
def _set_last_revision(self, revision_id):
3455
old_revno, old_revid = self.last_revision_info()
3456
# This performs additional work to meet the hook contract; while its
3457
# undesirable, we have to synthesise the revno to call the hook, and
3458
# not calling the hook is worse as it means changes can't be prevented.
3459
# Having calculated this though, we can't just call into
3460
# set_last_revision_info as a simple call, because there is a set_rh
3461
# hook that some folk may still be using.
3462
history = self._lefthand_history(revision_id)
3463
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3464
self._clear_cached_state()
3465
response = self._call('Branch.set_last_revision',
3466
self._remote_path(), self._lock_token, self._repo_lock_token,
3468
if response != ('ok',):
3469
raise errors.UnexpectedSmartServerResponse(response)
3470
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3472
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3474
def set_revision_history(self, rev_history):
3475
"""See Branch.set_revision_history."""
3476
self._set_revision_history(rev_history)
3479
def _set_revision_history(self, rev_history):
3480
# Send just the tip revision of the history; the server will generate
3481
# the full history from that. If the revision doesn't exist in this
3482
# branch, NoSuchRevision will be raised.
3483
if rev_history == []:
3486
rev_id = rev_history[-1]
3487
self._set_last_revision(rev_id)
3488
for hook in branch.Branch.hooks['set_rh']:
3489
hook(self, rev_history)
3490
self._cache_revision_history(rev_history)
3492
def _get_parent_location(self):
3493
medium = self._client._medium
3494
if medium._is_remote_before((1, 13)):
3495
return self._vfs_get_parent_location()
3497
response = self._call('Branch.get_parent', self._remote_path())
3498
except errors.UnknownSmartMethod:
3499
medium._remember_remote_is_before((1, 13))
3500
return self._vfs_get_parent_location()
3501
if len(response) != 1:
3502
raise errors.UnexpectedSmartServerResponse(response)
3503
parent_location = response[0]
3504
if parent_location == '':
3506
return parent_location
3508
def _vfs_get_parent_location(self):
3510
return self._real_branch._get_parent_location()
3512
def _set_parent_location(self, url):
3513
medium = self._client._medium
3514
if medium._is_remote_before((1, 15)):
3515
return self._vfs_set_parent_location(url)
3517
call_url = url or ''
3518
if type(call_url) is not str:
3519
raise AssertionError('url must be a str or None (%s)' % url)
3520
response = self._call('Branch.set_parent_location',
3521
self._remote_path(), self._lock_token, self._repo_lock_token,
3523
except errors.UnknownSmartMethod:
3524
medium._remember_remote_is_before((1, 15))
3525
return self._vfs_set_parent_location(url)
3527
raise errors.UnexpectedSmartServerResponse(response)
3529
def _vfs_set_parent_location(self, url):
3531
return self._real_branch._set_parent_location(url)
3534
def pull(self, source, overwrite=False, stop_revision=None,
3536
self._clear_cached_state_of_remote_branch_only()
3538
return self._real_branch.pull(
3539
source, overwrite=overwrite, stop_revision=stop_revision,
3540
_override_hook_target=self, **kwargs)
3543
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
3545
return self._real_branch.push(
3546
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3547
_override_hook_source_branch=self)
3549
def is_locked(self):
3550
return self._lock_count >= 1
3553
def revision_id_to_dotted_revno(self, revision_id):
3554
"""Given a revision id, return its dotted revno.
3556
:return: a tuple like (1,) or (400,1,3).
3559
response = self._call('Branch.revision_id_to_revno',
3560
self._remote_path(), revision_id)
3561
except errors.UnknownSmartMethod:
3563
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3564
if response[0] == 'ok':
3565
return tuple([int(x) for x in response[1:]])
3567
raise errors.UnexpectedSmartServerResponse(response)
3570
def revision_id_to_revno(self, revision_id):
3571
"""Given a revision id on the branch mainline, return its revno.
3576
response = self._call('Branch.revision_id_to_revno',
3577
self._remote_path(), revision_id)
3578
except errors.UnknownSmartMethod:
3580
return self._real_branch.revision_id_to_revno(revision_id)
3581
if response[0] == 'ok':
3582
if len(response) == 2:
3583
return int(response[1])
3584
raise NoSuchRevision(self, revision_id)
3586
raise errors.UnexpectedSmartServerResponse(response)
3589
def set_last_revision_info(self, revno, revision_id):
3590
# XXX: These should be returned by the set_last_revision_info verb
3591
old_revno, old_revid = self.last_revision_info()
3592
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3593
if not revision_id or not isinstance(revision_id, basestring):
3594
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3596
response = self._call('Branch.set_last_revision_info',
3597
self._remote_path(), self._lock_token, self._repo_lock_token,
3598
str(revno), revision_id)
3599
except errors.UnknownSmartMethod:
3601
self._clear_cached_state_of_remote_branch_only()
3602
self._real_branch.set_last_revision_info(revno, revision_id)
3603
self._last_revision_info_cache = revno, revision_id
3605
if response == ('ok',):
3606
self._clear_cached_state()
3607
self._last_revision_info_cache = revno, revision_id
3608
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3609
# Update the _real_branch's cache too.
3610
if self._real_branch is not None:
3611
cache = self._last_revision_info_cache
3612
self._real_branch._last_revision_info_cache = cache
3614
raise errors.UnexpectedSmartServerResponse(response)
3617
def generate_revision_history(self, revision_id, last_rev=None,
3619
medium = self._client._medium
3620
if not medium._is_remote_before((1, 6)):
3621
# Use a smart method for 1.6 and above servers
3623
self._set_last_revision_descendant(revision_id, other_branch,
3624
allow_diverged=True, allow_overwrite_descendant=True)
3626
except errors.UnknownSmartMethod:
3627
medium._remember_remote_is_before((1, 6))
3628
self._clear_cached_state_of_remote_branch_only()
3629
self._set_revision_history(self._lefthand_history(revision_id,
3630
last_rev=last_rev,other_branch=other_branch))
3632
def set_push_location(self, location):
3634
return self._real_branch.set_push_location(location)
3636
def heads_to_fetch(self):
3637
if self._format._use_default_local_heads_to_fetch():
3638
# We recognise this format, and its heads-to-fetch implementation
3639
# is the default one (tip + tags). In this case it's cheaper to
3640
# just use the default implementation rather than a special RPC as
3641
# the tip and tags data is cached.
3642
return branch.Branch.heads_to_fetch(self)
3643
medium = self._client._medium
3644
if medium._is_remote_before((2, 4)):
3645
return self._vfs_heads_to_fetch()
3647
return self._rpc_heads_to_fetch()
3648
except errors.UnknownSmartMethod:
3649
medium._remember_remote_is_before((2, 4))
3650
return self._vfs_heads_to_fetch()
3652
def _rpc_heads_to_fetch(self):
3653
response = self._call('Branch.heads_to_fetch', self._remote_path())
3654
if len(response) != 2:
3655
raise errors.UnexpectedSmartServerResponse(response)
3656
must_fetch, if_present_fetch = response
3657
return set(must_fetch), set(if_present_fetch)
3659
def _vfs_heads_to_fetch(self):
3661
return self._real_branch.heads_to_fetch()
3664
class RemoteConfig(object):
3665
"""A Config that reads and writes from smart verbs.
3667
It is a low-level object that considers config data to be name/value pairs
3668
that may be associated with a section. Assigning meaning to the these
3669
values is done at higher levels like bzrlib.config.TreeConfig.
3672
def get_option(self, name, section=None, default=None):
3673
"""Return the value associated with a named option.
3675
:param name: The name of the value
3676
:param section: The section the option is in (if any)
3677
:param default: The value to return if the value is not set
3678
:return: The value or default value
3681
configobj = self._get_configobj()
3684
section_obj = configobj
3687
section_obj = configobj[section]
3690
if section_obj is None:
3693
value = section_obj.get(name, default)
3694
except errors.UnknownSmartMethod:
3695
value = self._vfs_get_option(name, section, default)
3696
for hook in config.OldConfigHooks['get']:
3697
hook(self, name, value)
3700
def _response_to_configobj(self, response):
3701
if len(response[0]) and response[0][0] != 'ok':
3702
raise errors.UnexpectedSmartServerResponse(response)
3703
lines = response[1].read_body_bytes().splitlines()
3704
conf = config.ConfigObj(lines, encoding='utf-8')
3705
for hook in config.OldConfigHooks['load']:
3710
class RemoteBranchConfig(RemoteConfig):
3711
"""A RemoteConfig for Branches."""
3713
def __init__(self, branch):
3714
self._branch = branch
3716
def _get_configobj(self):
3717
path = self._branch._remote_path()
3718
response = self._branch._client.call_expecting_body(
3719
'Branch.get_config_file', path)
3720
return self._response_to_configobj(response)
3722
def set_option(self, value, name, section=None):
3723
"""Set the value associated with a named option.
3725
:param value: The value to set
3726
:param name: The name of the value to set
3727
:param section: The section the option is in (if any)
3729
medium = self._branch._client._medium
3730
if medium._is_remote_before((1, 14)):
3731
return self._vfs_set_option(value, name, section)
3732
if isinstance(value, dict):
3733
if medium._is_remote_before((2, 2)):
3734
return self._vfs_set_option(value, name, section)
3735
return self._set_config_option_dict(value, name, section)
3737
return self._set_config_option(value, name, section)
3739
def _set_config_option(self, value, name, section):
3741
path = self._branch._remote_path()
3742
response = self._branch._client.call('Branch.set_config_option',
3743
path, self._branch._lock_token, self._branch._repo_lock_token,
3744
value.encode('utf8'), name, section or '')
3745
except errors.UnknownSmartMethod:
3746
medium = self._branch._client._medium
3747
medium._remember_remote_is_before((1, 14))
3748
return self._vfs_set_option(value, name, section)
3750
raise errors.UnexpectedSmartServerResponse(response)
3752
def _serialize_option_dict(self, option_dict):
3754
for key, value in option_dict.items():
3755
if isinstance(key, unicode):
3756
key = key.encode('utf8')
3757
if isinstance(value, unicode):
3758
value = value.encode('utf8')
3759
utf8_dict[key] = value
3760
return bencode.bencode(utf8_dict)
3762
def _set_config_option_dict(self, value, name, section):
3764
path = self._branch._remote_path()
3765
serialised_dict = self._serialize_option_dict(value)
3766
response = self._branch._client.call(
3767
'Branch.set_config_option_dict',
3768
path, self._branch._lock_token, self._branch._repo_lock_token,
3769
serialised_dict, name, section or '')
3770
except errors.UnknownSmartMethod:
3771
medium = self._branch._client._medium
3772
medium._remember_remote_is_before((2, 2))
3773
return self._vfs_set_option(value, name, section)
3775
raise errors.UnexpectedSmartServerResponse(response)
3777
def _real_object(self):
3778
self._branch._ensure_real()
3779
return self._branch._real_branch
3781
def _vfs_set_option(self, value, name, section=None):
3782
return self._real_object()._get_config().set_option(
3783
value, name, section)
3786
class RemoteBzrDirConfig(RemoteConfig):
3787
"""A RemoteConfig for BzrDirs."""
3789
def __init__(self, bzrdir):
3790
self._bzrdir = bzrdir
3792
def _get_configobj(self):
3793
medium = self._bzrdir._client._medium
3794
verb = 'BzrDir.get_config_file'
3795
if medium._is_remote_before((1, 15)):
3796
raise errors.UnknownSmartMethod(verb)
3797
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
3798
response = self._bzrdir._call_expecting_body(
3800
return self._response_to_configobj(response)
3802
def _vfs_get_option(self, name, section, default):
3803
return self._real_object()._get_config().get_option(
3804
name, section, default)
3806
def set_option(self, value, name, section=None):
3807
"""Set the value associated with a named option.
3809
:param value: The value to set
3810
:param name: The name of the value to set
3811
:param section: The section the option is in (if any)
3813
return self._real_object()._get_config().set_option(
3814
value, name, section)
3816
def _real_object(self):
3817
self._bzrdir._ensure_real()
3818
return self._bzrdir._real_bzrdir
3821
def _extract_tar(tar, to_dir):
3822
"""Extract all the contents of a tarfile object.
3824
A replacement for extractall, which is not present in python2.4
3827
tar.extract(tarinfo, to_dir)
3830
error_translators = registry.Registry()
3831
no_context_error_translators = registry.Registry()
3834
def _translate_error(err, **context):
3835
"""Translate an ErrorFromSmartServer into a more useful error.
3837
Possible context keys:
3845
If the error from the server doesn't match a known pattern, then
3846
UnknownErrorFromSmartServer is raised.
3850
return context[name]
3851
except KeyError, key_err:
3852
mutter('Missing key %r in context %r', key_err.args[0], context)
3855
"""Get the path from the context if present, otherwise use first error
3859
return context['path']
3860
except KeyError, key_err:
3862
return err.error_args[0]
3863
except IndexError, idx_err:
3865
'Missing key %r in context %r', key_err.args[0], context)
3869
translator = error_translators.get(err.error_verb)
3873
raise translator(err, find, get_path)
3875
translator = no_context_error_translators.get(err.error_verb)
3877
raise errors.UnknownErrorFromSmartServer(err)
3879
raise translator(err)
3882
error_translators.register('NoSuchRevision',
3883
lambda err, find, get_path: NoSuchRevision(
3884
find('branch'), err.error_args[0]))
3885
error_translators.register('nosuchrevision',
3886
lambda err, find, get_path: NoSuchRevision(
3887
find('repository'), err.error_args[0]))
3889
def _translate_nobranch_error(err, find, get_path):
3890
if len(err.error_args) >= 1:
3891
extra = err.error_args[0]
3894
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
3897
error_translators.register('nobranch', _translate_nobranch_error)
3898
error_translators.register('norepository',
3899
lambda err, find, get_path: errors.NoRepositoryPresent(
3901
error_translators.register('UnlockableTransport',
3902
lambda err, find, get_path: errors.UnlockableTransport(
3903
find('bzrdir').root_transport))
3904
error_translators.register('TokenMismatch',
3905
lambda err, find, get_path: errors.TokenMismatch(
3906
find('token'), '(remote token)'))
3907
error_translators.register('Diverged',
3908
lambda err, find, get_path: errors.DivergedBranches(
3909
find('branch'), find('other_branch')))
3910
error_translators.register('NotStacked',
3911
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
3913
def _translate_PermissionDenied(err, find, get_path):
3915
if len(err.error_args) >= 2:
3916
extra = err.error_args[1]
3919
return errors.PermissionDenied(path, extra=extra)
3921
error_translators.register('PermissionDenied', _translate_PermissionDenied)
3922
error_translators.register('ReadError',
3923
lambda err, find, get_path: errors.ReadError(get_path()))
3924
error_translators.register('NoSuchFile',
3925
lambda err, find, get_path: errors.NoSuchFile(get_path()))
3926
error_translators.register('UnsuspendableWriteGroup',
3927
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
3928
repository=find('repository')))
3929
error_translators.register('UnresumableWriteGroup',
3930
lambda err, find, get_path: errors.UnresumableWriteGroup(
3931
repository=find('repository'), write_groups=err.error_args[0],
3932
reason=err.error_args[1]))
3933
no_context_error_translators.register('IncompatibleRepositories',
3934
lambda err: errors.IncompatibleRepositories(
3935
err.error_args[0], err.error_args[1], err.error_args[2]))
3936
no_context_error_translators.register('LockContention',
3937
lambda err: errors.LockContention('(remote lock)'))
3938
no_context_error_translators.register('LockFailed',
3939
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
3940
no_context_error_translators.register('TipChangeRejected',
3941
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
3942
no_context_error_translators.register('UnstackableBranchFormat',
3943
lambda err: errors.UnstackableBranchFormat(*err.error_args))
3944
no_context_error_translators.register('UnstackableRepositoryFormat',
3945
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
3946
no_context_error_translators.register('FileExists',
3947
lambda err: errors.FileExists(err.error_args[0]))
3948
no_context_error_translators.register('DirectoryNotEmpty',
3949
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
3951
def _translate_short_readv_error(err):
3952
args = err.error_args
3953
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
3956
no_context_error_translators.register('ShortReadvError',
3957
_translate_short_readv_error)
3959
def _translate_unicode_error(err):
3960
encoding = str(err.error_args[0]) # encoding must always be a string
3961
val = err.error_args[1]
3962
start = int(err.error_args[2])
3963
end = int(err.error_args[3])
3964
reason = str(err.error_args[4]) # reason must always be a string
3965
if val.startswith('u:'):
3966
val = val[2:].decode('utf-8')
3967
elif val.startswith('s:'):
3968
val = val[2:].decode('base64')
3969
if err.error_verb == 'UnicodeDecodeError':
3970
raise UnicodeDecodeError(encoding, val, start, end, reason)
3971
elif err.error_verb == 'UnicodeEncodeError':
3972
raise UnicodeEncodeError(encoding, val, start, end, reason)
3974
no_context_error_translators.register('UnicodeEncodeError',
3975
_translate_unicode_error)
3976
no_context_error_translators.register('UnicodeDecodeError',
3977
_translate_unicode_error)
3978
no_context_error_translators.register('ReadOnlyError',
3979
lambda err: errors.TransportNotPossible('readonly transport'))
3980
no_context_error_translators.register('MemoryError',
3981
lambda err: errors.BzrError("remote server out of memory\n"
3982
"Retry non-remotely, or contact the server admin for details."))
3983
no_context_error_translators.register('RevisionNotPresent',
3984
lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
3986
no_context_error_translators.register('BzrCheckError',
3987
lambda err: errors.BzrCheckError(msg=err.error_args[0]))