1
# Copyright (C) 2006-2012 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
25
bzrdir as _mod_bzrdir,
26
config as _mod_config,
37
repository as _mod_repository,
38
revision as _mod_revision,
41
testament as _mod_testament,
46
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
47
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
48
from bzrlib.errors import (
52
from bzrlib.i18n import gettext
53
from bzrlib.inventory import Inventory
54
from bzrlib.lockable_files import LockableFiles
55
from bzrlib.smart import client, vfs, repository as smart_repo
56
from bzrlib.smart.client import _SmartClient
57
from bzrlib.revision import NULL_REVISION
58
from bzrlib.revisiontree import InventoryRevisionTree
59
from bzrlib.repository import RepositoryWriteLockResult, _LazyListJoin
60
from bzrlib.serializer import format_registry as serializer_format_registry
61
from bzrlib.trace import mutter, note, warning, log_exception_quietly
62
from bzrlib.versionedfile import FulltextContentFactory
65
_DEFAULT_SEARCH_DEPTH = 100
68
class _RpcHelper(object):
69
"""Mixin class that helps with issuing RPCs."""
71
def _call(self, method, *args, **err_context):
73
return self._client.call(method, *args)
74
except errors.ErrorFromSmartServer, err:
75
self._translate_error(err, **err_context)
77
def _call_expecting_body(self, method, *args, **err_context):
79
return self._client.call_expecting_body(method, *args)
80
except errors.ErrorFromSmartServer, err:
81
self._translate_error(err, **err_context)
83
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
85
return self._client.call_with_body_bytes(method, args, body_bytes)
86
except errors.ErrorFromSmartServer, err:
87
self._translate_error(err, **err_context)
89
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
92
return self._client.call_with_body_bytes_expecting_body(
93
method, args, body_bytes)
94
except errors.ErrorFromSmartServer, err:
95
self._translate_error(err, **err_context)
98
def response_tuple_to_repo_format(response):
99
"""Convert a response tuple describing a repository format to a format."""
100
format = RemoteRepositoryFormat()
101
format._rich_root_data = (response[0] == 'yes')
102
format._supports_tree_reference = (response[1] == 'yes')
103
format._supports_external_lookups = (response[2] == 'yes')
104
format._network_name = response[3]
108
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
109
# does not have to be imported unless a remote format is involved.
111
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
112
"""Format representing bzrdirs accessed via a smart server"""
114
supports_workingtrees = False
116
colocated_branches = False
119
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
120
# XXX: It's a bit ugly that the network name is here, because we'd
121
# like to believe that format objects are stateless or at least
122
# immutable, However, we do at least avoid mutating the name after
123
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
124
self._network_name = None
127
return "%s(_network_name=%r)" % (self.__class__.__name__,
130
def get_format_description(self):
131
if self._network_name:
133
real_format = controldir.network_format_registry.get(
138
return 'Remote: ' + real_format.get_format_description()
139
return 'bzr remote bzrdir'
141
def get_format_string(self):
142
raise NotImplementedError(self.get_format_string)
144
def network_name(self):
145
if self._network_name:
146
return self._network_name
148
raise AssertionError("No network name set.")
150
def initialize_on_transport(self, transport):
152
# hand off the request to the smart server
153
client_medium = transport.get_smart_medium()
154
except errors.NoSmartMedium:
155
# TODO: lookup the local format from a server hint.
156
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
157
return local_dir_format.initialize_on_transport(transport)
158
client = _SmartClient(client_medium)
159
path = client.remote_path_from_transport(transport)
161
response = client.call('BzrDirFormat.initialize', path)
162
except errors.ErrorFromSmartServer, err:
163
_translate_error(err, path=path)
164
if response[0] != 'ok':
165
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
166
format = RemoteBzrDirFormat()
167
self._supply_sub_formats_to(format)
168
return RemoteBzrDir(transport, format)
170
def parse_NoneTrueFalse(self, arg):
177
raise AssertionError("invalid arg %r" % arg)
179
def _serialize_NoneTrueFalse(self, arg):
186
def _serialize_NoneString(self, arg):
189
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
190
create_prefix=False, force_new_repo=False, stacked_on=None,
191
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
194
# hand off the request to the smart server
195
client_medium = transport.get_smart_medium()
196
except errors.NoSmartMedium:
199
# Decline to open it if the server doesn't support our required
200
# version (3) so that the VFS-based transport will do it.
201
if client_medium.should_probe():
203
server_version = client_medium.protocol_version()
204
if server_version != '2':
208
except errors.SmartProtocolError:
209
# Apparently there's no usable smart server there, even though
210
# the medium supports the smart protocol.
215
client = _SmartClient(client_medium)
216
path = client.remote_path_from_transport(transport)
217
if client_medium._is_remote_before((1, 16)):
220
# TODO: lookup the local format from a server hint.
221
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
222
self._supply_sub_formats_to(local_dir_format)
223
return local_dir_format.initialize_on_transport_ex(transport,
224
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
225
force_new_repo=force_new_repo, stacked_on=stacked_on,
226
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
227
make_working_trees=make_working_trees, shared_repo=shared_repo,
229
return self._initialize_on_transport_ex_rpc(client, path, transport,
230
use_existing_dir, create_prefix, force_new_repo, stacked_on,
231
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
233
def _initialize_on_transport_ex_rpc(self, client, path, transport,
234
use_existing_dir, create_prefix, force_new_repo, stacked_on,
235
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
237
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
238
args.append(self._serialize_NoneTrueFalse(create_prefix))
239
args.append(self._serialize_NoneTrueFalse(force_new_repo))
240
args.append(self._serialize_NoneString(stacked_on))
241
# stack_on_pwd is often/usually our transport
244
stack_on_pwd = transport.relpath(stack_on_pwd)
247
except errors.PathNotChild:
249
args.append(self._serialize_NoneString(stack_on_pwd))
250
args.append(self._serialize_NoneString(repo_format_name))
251
args.append(self._serialize_NoneTrueFalse(make_working_trees))
252
args.append(self._serialize_NoneTrueFalse(shared_repo))
253
request_network_name = self._network_name or \
254
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
256
response = client.call('BzrDirFormat.initialize_ex_1.16',
257
request_network_name, path, *args)
258
except errors.UnknownSmartMethod:
259
client._medium._remember_remote_is_before((1,16))
260
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
261
self._supply_sub_formats_to(local_dir_format)
262
return local_dir_format.initialize_on_transport_ex(transport,
263
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
264
force_new_repo=force_new_repo, stacked_on=stacked_on,
265
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
266
make_working_trees=make_working_trees, shared_repo=shared_repo,
268
except errors.ErrorFromSmartServer, err:
269
_translate_error(err, path=path)
270
repo_path = response[0]
271
bzrdir_name = response[6]
272
require_stacking = response[7]
273
require_stacking = self.parse_NoneTrueFalse(require_stacking)
274
format = RemoteBzrDirFormat()
275
format._network_name = bzrdir_name
276
self._supply_sub_formats_to(format)
277
bzrdir = RemoteBzrDir(transport, format, _client=client)
279
repo_format = response_tuple_to_repo_format(response[1:])
283
repo_bzrdir_format = RemoteBzrDirFormat()
284
repo_bzrdir_format._network_name = response[5]
285
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
289
final_stack = response[8] or None
290
final_stack_pwd = response[9] or None
292
final_stack_pwd = urlutils.join(
293
transport.base, final_stack_pwd)
294
remote_repo = RemoteRepository(repo_bzr, repo_format)
295
if len(response) > 10:
296
# Updated server verb that locks remotely.
297
repo_lock_token = response[10] or None
298
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
300
remote_repo.dont_leave_lock_in_place()
302
remote_repo.lock_write()
303
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
304
final_stack_pwd, require_stacking)
305
policy.acquire_repository()
309
bzrdir._format.set_branch_format(self.get_branch_format())
311
# The repo has already been created, but we need to make sure that
312
# we'll make a stackable branch.
313
bzrdir._format.require_stacking(_skip_repo=True)
314
return remote_repo, bzrdir, require_stacking, policy
316
def _open(self, transport):
317
return RemoteBzrDir(transport, self)
319
def __eq__(self, other):
320
if not isinstance(other, RemoteBzrDirFormat):
322
return self.get_format_description() == other.get_format_description()
324
def __return_repository_format(self):
325
# Always return a RemoteRepositoryFormat object, but if a specific bzr
326
# repository format has been asked for, tell the RemoteRepositoryFormat
327
# that it should use that for init() etc.
328
result = RemoteRepositoryFormat()
329
custom_format = getattr(self, '_repository_format', None)
331
if isinstance(custom_format, RemoteRepositoryFormat):
334
# We will use the custom format to create repositories over the
335
# wire; expose its details like rich_root_data for code to
337
result._custom_format = custom_format
340
def get_branch_format(self):
341
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
342
if not isinstance(result, RemoteBranchFormat):
343
new_result = RemoteBranchFormat()
344
new_result._custom_format = result
346
self.set_branch_format(new_result)
350
repository_format = property(__return_repository_format,
351
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
354
class RemoteControlStore(_mod_config.IniFileStore):
355
"""Control store which attempts to use HPSS calls to retrieve control store.
357
Note that this is specific to bzr-based formats.
360
def __init__(self, bzrdir):
361
super(RemoteControlStore, self).__init__()
363
self._real_store = None
365
def lock_write(self, token=None):
367
return self._real_store.lock_write(token)
371
return self._real_store.unlock()
375
# We need to be able to override the undecorated implementation
376
self.save_without_locking()
378
def save_without_locking(self):
379
super(RemoteControlStore, self).save()
381
def _ensure_real(self):
382
self.bzrdir._ensure_real()
383
if self._real_store is None:
384
self._real_store = _mod_config.ControlStore(self.bzrdir)
386
def external_url(self):
387
return self.bzrdir.user_url
389
def _load_content(self):
390
medium = self.bzrdir._client._medium
391
path = self.bzrdir._path_for_remote_call(self.bzrdir._client)
393
response, handler = self.bzrdir._call_expecting_body(
394
'BzrDir.get_config_file', path)
395
except errors.UnknownSmartMethod:
397
return self._real_store._load_content()
398
if len(response) and response[0] != 'ok':
399
raise errors.UnexpectedSmartServerResponse(response)
400
return handler.read_body_bytes()
402
def _save_content(self, content):
403
# FIXME JRV 2011-11-22: Ideally this should use a
404
# HPSS call too, but at the moment it is not possible
405
# to write lock control directories.
407
return self._real_store._save_content(content)
410
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
411
"""Control directory on a remote server, accessed via bzr:// or similar."""
413
def __init__(self, transport, format, _client=None, _force_probe=False):
414
"""Construct a RemoteBzrDir.
416
:param _client: Private parameter for testing. Disables probing and the
417
use of a real bzrdir.
419
_mod_bzrdir.BzrDir.__init__(self, transport, format)
420
# this object holds a delegated bzrdir that uses file-level operations
421
# to talk to the other side
422
self._real_bzrdir = None
423
self._has_working_tree = None
424
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
425
# create_branch for details.
426
self._next_open_branch_result = None
429
medium = transport.get_smart_medium()
430
self._client = client._SmartClient(medium)
432
self._client = _client
439
return '%s(%r)' % (self.__class__.__name__, self._client)
441
def _probe_bzrdir(self):
442
medium = self._client._medium
443
path = self._path_for_remote_call(self._client)
444
if medium._is_remote_before((2, 1)):
448
self._rpc_open_2_1(path)
450
except errors.UnknownSmartMethod:
451
medium._remember_remote_is_before((2, 1))
454
def _rpc_open_2_1(self, path):
455
response = self._call('BzrDir.open_2.1', path)
456
if response == ('no',):
457
raise errors.NotBranchError(path=self.root_transport.base)
458
elif response[0] == 'yes':
459
if response[1] == 'yes':
460
self._has_working_tree = True
461
elif response[1] == 'no':
462
self._has_working_tree = False
464
raise errors.UnexpectedSmartServerResponse(response)
466
raise errors.UnexpectedSmartServerResponse(response)
468
def _rpc_open(self, path):
469
response = self._call('BzrDir.open', path)
470
if response not in [('yes',), ('no',)]:
471
raise errors.UnexpectedSmartServerResponse(response)
472
if response == ('no',):
473
raise errors.NotBranchError(path=self.root_transport.base)
475
def _ensure_real(self):
476
"""Ensure that there is a _real_bzrdir set.
478
Used before calls to self._real_bzrdir.
480
if not self._real_bzrdir:
481
if 'hpssvfs' in debug.debug_flags:
483
warning('VFS BzrDir access triggered\n%s',
484
''.join(traceback.format_stack()))
485
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
486
self.root_transport, probers=[_mod_bzrdir.BzrProber])
487
self._format._network_name = \
488
self._real_bzrdir._format.network_name()
490
def _translate_error(self, err, **context):
491
_translate_error(err, bzrdir=self, **context)
493
def break_lock(self):
494
# Prevent aliasing problems in the next_open_branch_result cache.
495
# See create_branch for rationale.
496
self._next_open_branch_result = None
497
return _mod_bzrdir.BzrDir.break_lock(self)
499
def _vfs_checkout_metadir(self):
501
return self._real_bzrdir.checkout_metadir()
503
def checkout_metadir(self):
504
"""Retrieve the controldir format to use for checkouts of this one.
506
medium = self._client._medium
507
if medium._is_remote_before((2, 5)):
508
return self._vfs_checkout_metadir()
509
path = self._path_for_remote_call(self._client)
511
response = self._client.call('BzrDir.checkout_metadir',
513
except errors.UnknownSmartMethod:
514
medium._remember_remote_is_before((2, 5))
515
return self._vfs_checkout_metadir()
516
if len(response) != 3:
517
raise errors.UnexpectedSmartServerResponse(response)
518
control_name, repo_name, branch_name = response
520
format = controldir.network_format_registry.get(control_name)
522
raise errors.UnknownFormatError(kind='control',
526
repo_format = _mod_repository.network_format_registry.get(
529
raise errors.UnknownFormatError(kind='repository',
531
format.repository_format = repo_format
534
format.set_branch_format(
535
branch.network_format_registry.get(branch_name))
537
raise errors.UnknownFormatError(kind='branch',
541
def _vfs_cloning_metadir(self, require_stacking=False):
543
return self._real_bzrdir.cloning_metadir(
544
require_stacking=require_stacking)
546
def cloning_metadir(self, require_stacking=False):
547
medium = self._client._medium
548
if medium._is_remote_before((1, 13)):
549
return self._vfs_cloning_metadir(require_stacking=require_stacking)
550
verb = 'BzrDir.cloning_metadir'
555
path = self._path_for_remote_call(self._client)
557
response = self._call(verb, path, stacking)
558
except errors.UnknownSmartMethod:
559
medium._remember_remote_is_before((1, 13))
560
return self._vfs_cloning_metadir(require_stacking=require_stacking)
561
except errors.UnknownErrorFromSmartServer, err:
562
if err.error_tuple != ('BranchReference',):
564
# We need to resolve the branch reference to determine the
565
# cloning_metadir. This causes unnecessary RPCs to open the
566
# referenced branch (and bzrdir, etc) but only when the caller
567
# didn't already resolve the branch reference.
568
referenced_branch = self.open_branch()
569
return referenced_branch.bzrdir.cloning_metadir()
570
if len(response) != 3:
571
raise errors.UnexpectedSmartServerResponse(response)
572
control_name, repo_name, branch_info = response
573
if len(branch_info) != 2:
574
raise errors.UnexpectedSmartServerResponse(response)
575
branch_ref, branch_name = branch_info
577
format = controldir.network_format_registry.get(control_name)
579
raise errors.UnknownFormatError(kind='control', format=control_name)
583
format.repository_format = _mod_repository.network_format_registry.get(
586
raise errors.UnknownFormatError(kind='repository',
588
if branch_ref == 'ref':
589
# XXX: we need possible_transports here to avoid reopening the
590
# connection to the referenced location
591
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
592
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
593
format.set_branch_format(branch_format)
594
elif branch_ref == 'branch':
597
branch_format = branch.network_format_registry.get(
600
raise errors.UnknownFormatError(kind='branch',
602
format.set_branch_format(branch_format)
604
raise errors.UnexpectedSmartServerResponse(response)
607
def create_repository(self, shared=False):
608
# as per meta1 formats - just delegate to the format object which may
610
result = self._format.repository_format.initialize(self, shared)
611
if not isinstance(result, RemoteRepository):
612
return self.open_repository()
616
def destroy_repository(self):
617
"""See BzrDir.destroy_repository"""
618
path = self._path_for_remote_call(self._client)
620
response = self._call('BzrDir.destroy_repository', path)
621
except errors.UnknownSmartMethod:
623
self._real_bzrdir.destroy_repository()
625
if response[0] != 'ok':
626
raise SmartProtocolError('unexpected response code %s' % (response,))
628
def create_branch(self, name=None, repository=None,
629
append_revisions_only=None):
631
name = self._get_selected_branch()
633
raise errors.NoColocatedBranchSupport(self)
634
# as per meta1 formats - just delegate to the format object which may
636
real_branch = self._format.get_branch_format().initialize(self,
637
name=name, repository=repository,
638
append_revisions_only=append_revisions_only)
639
if not isinstance(real_branch, RemoteBranch):
640
if not isinstance(repository, RemoteRepository):
641
raise AssertionError(
642
'need a RemoteRepository to use with RemoteBranch, got %r'
644
result = RemoteBranch(self, repository, real_branch, name=name)
647
# BzrDir.clone_on_transport() uses the result of create_branch but does
648
# not return it to its callers; we save approximately 8% of our round
649
# trips by handing the branch we created back to the first caller to
650
# open_branch rather than probing anew. Long term we need a API in
651
# bzrdir that doesn't discard result objects (like result_branch).
653
self._next_open_branch_result = result
656
def destroy_branch(self, name=None):
657
"""See BzrDir.destroy_branch"""
659
name = self._get_selected_branch()
661
raise errors.NoColocatedBranchSupport(self)
662
path = self._path_for_remote_call(self._client)
668
response = self._call('BzrDir.destroy_branch', path, *args)
669
except errors.UnknownSmartMethod:
671
self._real_bzrdir.destroy_branch(name=name)
672
self._next_open_branch_result = None
674
self._next_open_branch_result = None
675
if response[0] != 'ok':
676
raise SmartProtocolError('unexpected response code %s' % (response,))
678
def create_workingtree(self, revision_id=None, from_branch=None,
679
accelerator_tree=None, hardlink=False):
680
raise errors.NotLocalUrl(self.transport.base)
682
def find_branch_format(self, name=None):
683
"""Find the branch 'format' for this bzrdir.
685
This might be a synthetic object for e.g. RemoteBranch and SVN.
687
b = self.open_branch(name=name)
690
def get_branches(self, possible_transports=None, ignore_fallbacks=False):
691
path = self._path_for_remote_call(self._client)
693
response, handler = self._call_expecting_body(
694
'BzrDir.get_branches', path)
695
except errors.UnknownSmartMethod:
697
return self._real_bzrdir.get_branches()
698
if response[0] != "success":
699
raise errors.UnexpectedSmartServerResponse(response)
700
body = bencode.bdecode(handler.read_body_bytes())
702
for (name, value) in body.iteritems():
703
ret[name] = self._open_branch(name, value[0], value[1],
704
possible_transports=possible_transports,
705
ignore_fallbacks=ignore_fallbacks)
708
def set_branch_reference(self, target_branch, name=None):
709
"""See BzrDir.set_branch_reference()."""
711
name = self._get_selected_branch()
713
raise errors.NoColocatedBranchSupport(self)
715
return self._real_bzrdir.set_branch_reference(target_branch, name=name)
717
def get_branch_reference(self, name=None):
718
"""See BzrDir.get_branch_reference()."""
720
name = self._get_selected_branch()
722
raise errors.NoColocatedBranchSupport(self)
723
response = self._get_branch_reference()
724
if response[0] == 'ref':
729
def _get_branch_reference(self):
730
path = self._path_for_remote_call(self._client)
731
medium = self._client._medium
733
('BzrDir.open_branchV3', (2, 1)),
734
('BzrDir.open_branchV2', (1, 13)),
735
('BzrDir.open_branch', None),
737
for verb, required_version in candidate_calls:
738
if required_version and medium._is_remote_before(required_version):
741
response = self._call(verb, path)
742
except errors.UnknownSmartMethod:
743
if required_version is None:
745
medium._remember_remote_is_before(required_version)
748
if verb == 'BzrDir.open_branch':
749
if response[0] != 'ok':
750
raise errors.UnexpectedSmartServerResponse(response)
751
if response[1] != '':
752
return ('ref', response[1])
754
return ('branch', '')
755
if response[0] not in ('ref', 'branch'):
756
raise errors.UnexpectedSmartServerResponse(response)
759
def _get_tree_branch(self, name=None):
760
"""See BzrDir._get_tree_branch()."""
761
return None, self.open_branch(name=name)
763
def _open_branch(self, name, kind, location_or_format,
764
ignore_fallbacks=False, possible_transports=None):
766
# a branch reference, use the existing BranchReference logic.
767
format = BranchReferenceFormat()
768
return format.open(self, name=name, _found=True,
769
location=location_or_format, ignore_fallbacks=ignore_fallbacks,
770
possible_transports=possible_transports)
771
branch_format_name = location_or_format
772
if not branch_format_name:
773
branch_format_name = None
774
format = RemoteBranchFormat(network_name=branch_format_name)
775
return RemoteBranch(self, self.find_repository(), format=format,
776
setup_stacking=not ignore_fallbacks, name=name,
777
possible_transports=possible_transports)
779
def open_branch(self, name=None, unsupported=False,
780
ignore_fallbacks=False, possible_transports=None):
782
name = self._get_selected_branch()
784
raise errors.NoColocatedBranchSupport(self)
786
raise NotImplementedError('unsupported flag support not implemented yet.')
787
if self._next_open_branch_result is not None:
788
# See create_branch for details.
789
result = self._next_open_branch_result
790
self._next_open_branch_result = None
792
response = self._get_branch_reference()
793
return self._open_branch(name, response[0], response[1],
794
possible_transports=possible_transports,
795
ignore_fallbacks=ignore_fallbacks)
797
def _open_repo_v1(self, path):
798
verb = 'BzrDir.find_repository'
799
response = self._call(verb, path)
800
if response[0] != 'ok':
801
raise errors.UnexpectedSmartServerResponse(response)
802
# servers that only support the v1 method don't support external
805
repo = self._real_bzrdir.open_repository()
806
response = response + ('no', repo._format.network_name())
807
return response, repo
809
def _open_repo_v2(self, path):
810
verb = 'BzrDir.find_repositoryV2'
811
response = self._call(verb, path)
812
if response[0] != 'ok':
813
raise errors.UnexpectedSmartServerResponse(response)
815
repo = self._real_bzrdir.open_repository()
816
response = response + (repo._format.network_name(),)
817
return response, repo
819
def _open_repo_v3(self, path):
820
verb = 'BzrDir.find_repositoryV3'
821
medium = self._client._medium
822
if medium._is_remote_before((1, 13)):
823
raise errors.UnknownSmartMethod(verb)
825
response = self._call(verb, path)
826
except errors.UnknownSmartMethod:
827
medium._remember_remote_is_before((1, 13))
829
if response[0] != 'ok':
830
raise errors.UnexpectedSmartServerResponse(response)
831
return response, None
833
def open_repository(self):
834
path = self._path_for_remote_call(self._client)
836
for probe in [self._open_repo_v3, self._open_repo_v2,
839
response, real_repo = probe(path)
841
except errors.UnknownSmartMethod:
844
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
845
if response[0] != 'ok':
846
raise errors.UnexpectedSmartServerResponse(response)
847
if len(response) != 6:
848
raise SmartProtocolError('incorrect response length %s' % (response,))
849
if response[1] == '':
850
# repo is at this dir.
851
format = response_tuple_to_repo_format(response[2:])
852
# Used to support creating a real format instance when needed.
853
format._creating_bzrdir = self
854
remote_repo = RemoteRepository(self, format)
855
format._creating_repo = remote_repo
856
if real_repo is not None:
857
remote_repo._set_real_repository(real_repo)
860
raise errors.NoRepositoryPresent(self)
862
def has_workingtree(self):
863
if self._has_working_tree is None:
864
path = self._path_for_remote_call(self._client)
866
response = self._call('BzrDir.has_workingtree', path)
867
except errors.UnknownSmartMethod:
869
self._has_working_tree = self._real_bzrdir.has_workingtree()
871
if response[0] not in ('yes', 'no'):
872
raise SmartProtocolError('unexpected response code %s' % (response,))
873
self._has_working_tree = (response[0] == 'yes')
874
return self._has_working_tree
876
def open_workingtree(self, recommend_upgrade=True):
877
if self.has_workingtree():
878
raise errors.NotLocalUrl(self.root_transport)
880
raise errors.NoWorkingTree(self.root_transport.base)
882
def _path_for_remote_call(self, client):
883
"""Return the path to be used for this bzrdir in a remote call."""
884
return urlutils.split_segment_parameters_raw(
885
client.remote_path_from_transport(self.root_transport))[0]
887
def get_branch_transport(self, branch_format, name=None):
889
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
891
def get_repository_transport(self, repository_format):
893
return self._real_bzrdir.get_repository_transport(repository_format)
895
def get_workingtree_transport(self, workingtree_format):
897
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
899
def can_convert_format(self):
900
"""Upgrading of remote bzrdirs is not supported yet."""
903
def needs_format_conversion(self, format):
904
"""Upgrading of remote bzrdirs is not supported yet."""
907
def _get_config(self):
908
return RemoteBzrDirConfig(self)
910
def _get_config_store(self):
911
return RemoteControlStore(self)
914
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
915
"""Format for repositories accessed over a _SmartClient.
917
Instances of this repository are represented by RemoteRepository
920
The RemoteRepositoryFormat is parameterized during construction
921
to reflect the capabilities of the real, remote format. Specifically
922
the attributes rich_root_data and supports_tree_reference are set
923
on a per instance basis, and are not set (and should not be) at
926
:ivar _custom_format: If set, a specific concrete repository format that
927
will be used when initializing a repository with this
928
RemoteRepositoryFormat.
929
:ivar _creating_repo: If set, the repository object that this
930
RemoteRepositoryFormat was created for: it can be called into
931
to obtain data like the network name.
934
_matchingbzrdir = RemoteBzrDirFormat()
935
supports_full_versioned_files = True
936
supports_leaving_lock = True
939
_mod_repository.RepositoryFormat.__init__(self)
940
self._custom_format = None
941
self._network_name = None
942
self._creating_bzrdir = None
943
self._revision_graph_can_have_wrong_parents = None
944
self._supports_chks = None
945
self._supports_external_lookups = None
946
self._supports_tree_reference = None
947
self._supports_funky_characters = None
948
self._supports_nesting_repositories = None
949
self._rich_root_data = None
952
return "%s(_network_name=%r)" % (self.__class__.__name__,
956
def fast_deltas(self):
958
return self._custom_format.fast_deltas
961
def rich_root_data(self):
962
if self._rich_root_data is None:
964
self._rich_root_data = self._custom_format.rich_root_data
965
return self._rich_root_data
968
def supports_chks(self):
969
if self._supports_chks is None:
971
self._supports_chks = self._custom_format.supports_chks
972
return self._supports_chks
975
def supports_external_lookups(self):
976
if self._supports_external_lookups is None:
978
self._supports_external_lookups = \
979
self._custom_format.supports_external_lookups
980
return self._supports_external_lookups
983
def supports_funky_characters(self):
984
if self._supports_funky_characters is None:
986
self._supports_funky_characters = \
987
self._custom_format.supports_funky_characters
988
return self._supports_funky_characters
991
def supports_nesting_repositories(self):
992
if self._supports_nesting_repositories is None:
994
self._supports_nesting_repositories = \
995
self._custom_format.supports_nesting_repositories
996
return self._supports_nesting_repositories
999
def supports_tree_reference(self):
1000
if self._supports_tree_reference is None:
1002
self._supports_tree_reference = \
1003
self._custom_format.supports_tree_reference
1004
return self._supports_tree_reference
1007
def revision_graph_can_have_wrong_parents(self):
1008
if self._revision_graph_can_have_wrong_parents is None:
1010
self._revision_graph_can_have_wrong_parents = \
1011
self._custom_format.revision_graph_can_have_wrong_parents
1012
return self._revision_graph_can_have_wrong_parents
1014
def _vfs_initialize(self, a_bzrdir, shared):
1015
"""Helper for common code in initialize."""
1016
if self._custom_format:
1017
# Custom format requested
1018
result = self._custom_format.initialize(a_bzrdir, shared=shared)
1019
elif self._creating_bzrdir is not None:
1020
# Use the format that the repository we were created to back
1022
prior_repo = self._creating_bzrdir.open_repository()
1023
prior_repo._ensure_real()
1024
result = prior_repo._real_repository._format.initialize(
1025
a_bzrdir, shared=shared)
1027
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
1028
# support remote initialization.
1029
# We delegate to a real object at this point (as RemoteBzrDir
1030
# delegate to the repository format which would lead to infinite
1031
# recursion if we just called a_bzrdir.create_repository.
1032
a_bzrdir._ensure_real()
1033
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
1034
if not isinstance(result, RemoteRepository):
1035
return self.open(a_bzrdir)
1039
def initialize(self, a_bzrdir, shared=False):
1040
# Being asked to create on a non RemoteBzrDir:
1041
if not isinstance(a_bzrdir, RemoteBzrDir):
1042
return self._vfs_initialize(a_bzrdir, shared)
1043
medium = a_bzrdir._client._medium
1044
if medium._is_remote_before((1, 13)):
1045
return self._vfs_initialize(a_bzrdir, shared)
1046
# Creating on a remote bzr dir.
1047
# 1) get the network name to use.
1048
if self._custom_format:
1049
network_name = self._custom_format.network_name()
1050
elif self._network_name:
1051
network_name = self._network_name
1053
# Select the current bzrlib default and ask for that.
1054
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
1055
reference_format = reference_bzrdir_format.repository_format
1056
network_name = reference_format.network_name()
1057
# 2) try direct creation via RPC
1058
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1059
verb = 'BzrDir.create_repository'
1063
shared_str = 'False'
1065
response = a_bzrdir._call(verb, path, network_name, shared_str)
1066
except errors.UnknownSmartMethod:
1067
# Fallback - use vfs methods
1068
medium._remember_remote_is_before((1, 13))
1069
return self._vfs_initialize(a_bzrdir, shared)
1071
# Turn the response into a RemoteRepository object.
1072
format = response_tuple_to_repo_format(response[1:])
1073
# Used to support creating a real format instance when needed.
1074
format._creating_bzrdir = a_bzrdir
1075
remote_repo = RemoteRepository(a_bzrdir, format)
1076
format._creating_repo = remote_repo
1079
def open(self, a_bzrdir):
1080
if not isinstance(a_bzrdir, RemoteBzrDir):
1081
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
1082
return a_bzrdir.open_repository()
1084
def _ensure_real(self):
1085
if self._custom_format is None:
1087
self._custom_format = _mod_repository.network_format_registry.get(
1090
raise errors.UnknownFormatError(kind='repository',
1091
format=self._network_name)
1094
def _fetch_order(self):
1096
return self._custom_format._fetch_order
1099
def _fetch_uses_deltas(self):
1101
return self._custom_format._fetch_uses_deltas
1104
def _fetch_reconcile(self):
1106
return self._custom_format._fetch_reconcile
1108
def get_format_description(self):
1110
return 'Remote: ' + self._custom_format.get_format_description()
1112
def __eq__(self, other):
1113
return self.__class__ is other.__class__
1115
def network_name(self):
1116
if self._network_name:
1117
return self._network_name
1118
self._creating_repo._ensure_real()
1119
return self._creating_repo._real_repository._format.network_name()
1122
def pack_compresses(self):
1124
return self._custom_format.pack_compresses
1127
def _serializer(self):
1129
return self._custom_format._serializer
1132
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1133
lock._RelockDebugMixin):
1134
"""Repository accessed over rpc.
1136
For the moment most operations are performed using local transport-backed
1140
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
1141
"""Create a RemoteRepository instance.
1143
:param remote_bzrdir: The bzrdir hosting this repository.
1144
:param format: The RemoteFormat object to use.
1145
:param real_repository: If not None, a local implementation of the
1146
repository logic for the repository, usually accessing the data
1148
:param _client: Private testing parameter - override the smart client
1149
to be used by the repository.
1152
self._real_repository = real_repository
1154
self._real_repository = None
1155
self.bzrdir = remote_bzrdir
1157
self._client = remote_bzrdir._client
1159
self._client = _client
1160
self._format = format
1161
self._lock_mode = None
1162
self._lock_token = None
1163
self._write_group_tokens = None
1164
self._lock_count = 0
1165
self._leave_lock = False
1166
# Cache of revision parents; misses are cached during read locks, and
1167
# write locks when no _real_repository has been set.
1168
self._unstacked_provider = graph.CachingParentsProvider(
1169
get_parent_map=self._get_parent_map_rpc)
1170
self._unstacked_provider.disable_cache()
1172
# These depend on the actual remote format, so force them off for
1173
# maximum compatibility. XXX: In future these should depend on the
1174
# remote repository instance, but this is irrelevant until we perform
1175
# reconcile via an RPC call.
1176
self._reconcile_does_inventory_gc = False
1177
self._reconcile_fixes_text_parents = False
1178
self._reconcile_backsup_inventory = False
1179
self.base = self.bzrdir.transport.base
1180
# Additional places to query for data.
1181
self._fallback_repositories = []
1184
def user_transport(self):
1185
return self.bzrdir.user_transport
1188
def control_transport(self):
1189
# XXX: Normally you shouldn't directly get at the remote repository
1190
# transport, but I'm not sure it's worth making this method
1191
# optional -- mbp 2010-04-21
1192
return self.bzrdir.get_repository_transport(None)
1195
return "%s(%s)" % (self.__class__.__name__, self.base)
1199
def abort_write_group(self, suppress_errors=False):
1200
"""Complete a write group on the decorated repository.
1202
Smart methods perform operations in a single step so this API
1203
is not really applicable except as a compatibility thunk
1204
for older plugins that don't use e.g. the CommitBuilder
1207
:param suppress_errors: see Repository.abort_write_group.
1209
if self._real_repository:
1211
return self._real_repository.abort_write_group(
1212
suppress_errors=suppress_errors)
1213
if not self.is_in_write_group():
1215
mutter('(suppressed) not in write group')
1217
raise errors.BzrError("not in write group")
1218
path = self.bzrdir._path_for_remote_call(self._client)
1220
response = self._call('Repository.abort_write_group', path,
1221
self._lock_token, self._write_group_tokens)
1222
except Exception, exc:
1223
self._write_group = None
1224
if not suppress_errors:
1226
mutter('abort_write_group failed')
1227
log_exception_quietly()
1228
note(gettext('bzr: ERROR (ignored): %s'), exc)
1230
if response != ('ok', ):
1231
raise errors.UnexpectedSmartServerResponse(response)
1232
self._write_group_tokens = None
1235
def chk_bytes(self):
1236
"""Decorate the real repository for now.
1238
In the long term a full blown network facility is needed to avoid
1239
creating a real repository object locally.
1242
return self._real_repository.chk_bytes
1244
def commit_write_group(self):
1245
"""Complete a write group on the decorated repository.
1247
Smart methods perform operations in a single step so this API
1248
is not really applicable except as a compatibility thunk
1249
for older plugins that don't use e.g. the CommitBuilder
1252
if self._real_repository:
1254
return self._real_repository.commit_write_group()
1255
if not self.is_in_write_group():
1256
raise errors.BzrError("not in write group")
1257
path = self.bzrdir._path_for_remote_call(self._client)
1258
response = self._call('Repository.commit_write_group', path,
1259
self._lock_token, self._write_group_tokens)
1260
if response != ('ok', ):
1261
raise errors.UnexpectedSmartServerResponse(response)
1262
self._write_group_tokens = None
1263
# Refresh data after writing to the repository.
1266
def resume_write_group(self, tokens):
1267
if self._real_repository:
1268
return self._real_repository.resume_write_group(tokens)
1269
path = self.bzrdir._path_for_remote_call(self._client)
1271
response = self._call('Repository.check_write_group', path,
1272
self._lock_token, tokens)
1273
except errors.UnknownSmartMethod:
1275
return self._real_repository.resume_write_group(tokens)
1276
if response != ('ok', ):
1277
raise errors.UnexpectedSmartServerResponse(response)
1278
self._write_group_tokens = tokens
1280
def suspend_write_group(self):
1281
if self._real_repository:
1282
return self._real_repository.suspend_write_group()
1283
ret = self._write_group_tokens or []
1284
self._write_group_tokens = None
1287
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1289
return self._real_repository.get_missing_parent_inventories(
1290
check_for_missing_texts=check_for_missing_texts)
1292
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1294
return self._real_repository.get_rev_id_for_revno(
1297
def get_rev_id_for_revno(self, revno, known_pair):
1298
"""See Repository.get_rev_id_for_revno."""
1299
path = self.bzrdir._path_for_remote_call(self._client)
1301
if self._client._medium._is_remote_before((1, 17)):
1302
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1303
response = self._call(
1304
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1305
except errors.UnknownSmartMethod:
1306
self._client._medium._remember_remote_is_before((1, 17))
1307
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1308
if response[0] == 'ok':
1309
return True, response[1]
1310
elif response[0] == 'history-incomplete':
1311
known_pair = response[1:3]
1312
for fallback in self._fallback_repositories:
1313
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1318
# Not found in any fallbacks
1319
return False, known_pair
1321
raise errors.UnexpectedSmartServerResponse(response)
1323
def _ensure_real(self):
1324
"""Ensure that there is a _real_repository set.
1326
Used before calls to self._real_repository.
1328
Note that _ensure_real causes many roundtrips to the server which are
1329
not desirable, and prevents the use of smart one-roundtrip RPC's to
1330
perform complex operations (such as accessing parent data, streaming
1331
revisions etc). Adding calls to _ensure_real should only be done when
1332
bringing up new functionality, adding fallbacks for smart methods that
1333
require a fallback path, and never to replace an existing smart method
1334
invocation. If in doubt chat to the bzr network team.
1336
if self._real_repository is None:
1337
if 'hpssvfs' in debug.debug_flags:
1339
warning('VFS Repository access triggered\n%s',
1340
''.join(traceback.format_stack()))
1341
self._unstacked_provider.missing_keys.clear()
1342
self.bzrdir._ensure_real()
1343
self._set_real_repository(
1344
self.bzrdir._real_bzrdir.open_repository())
1346
def _translate_error(self, err, **context):
1347
self.bzrdir._translate_error(err, repository=self, **context)
1349
def find_text_key_references(self):
1350
"""Find the text key references within the repository.
1352
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1353
to whether they were referred to by the inventory of the
1354
revision_id that they contain. The inventory texts from all present
1355
revision ids are assessed to generate this report.
1358
return self._real_repository.find_text_key_references()
1360
def _generate_text_key_index(self):
1361
"""Generate a new text key index for the repository.
1363
This is an expensive function that will take considerable time to run.
1365
:return: A dict mapping (file_id, revision_id) tuples to a list of
1366
parents, also (file_id, revision_id) tuples.
1369
return self._real_repository._generate_text_key_index()
1371
def _get_revision_graph(self, revision_id):
1372
"""Private method for using with old (< 1.2) servers to fallback."""
1373
if revision_id is None:
1375
elif _mod_revision.is_null(revision_id):
1378
path = self.bzrdir._path_for_remote_call(self._client)
1379
response = self._call_expecting_body(
1380
'Repository.get_revision_graph', path, revision_id)
1381
response_tuple, response_handler = response
1382
if response_tuple[0] != 'ok':
1383
raise errors.UnexpectedSmartServerResponse(response_tuple)
1384
coded = response_handler.read_body_bytes()
1386
# no revisions in this repository!
1388
lines = coded.split('\n')
1391
d = tuple(line.split())
1392
revision_graph[d[0]] = d[1:]
1394
return revision_graph
1396
def _get_sink(self):
1397
"""See Repository._get_sink()."""
1398
return RemoteStreamSink(self)
1400
def _get_source(self, to_format):
1401
"""Return a source for streaming from this repository."""
1402
return RemoteStreamSource(self, to_format)
1405
def get_file_graph(self):
1406
return graph.Graph(self.texts)
1409
def has_revision(self, revision_id):
1410
"""True if this repository has a copy of the revision."""
1411
# Copy of bzrlib.repository.Repository.has_revision
1412
return revision_id in self.has_revisions((revision_id,))
1415
def has_revisions(self, revision_ids):
1416
"""Probe to find out the presence of multiple revisions.
1418
:param revision_ids: An iterable of revision_ids.
1419
:return: A set of the revision_ids that were present.
1421
# Copy of bzrlib.repository.Repository.has_revisions
1422
parent_map = self.get_parent_map(revision_ids)
1423
result = set(parent_map)
1424
if _mod_revision.NULL_REVISION in revision_ids:
1425
result.add(_mod_revision.NULL_REVISION)
1428
def _has_same_fallbacks(self, other_repo):
1429
"""Returns true if the repositories have the same fallbacks."""
1430
# XXX: copied from Repository; it should be unified into a base class
1431
# <https://bugs.launchpad.net/bzr/+bug/401622>
1432
my_fb = self._fallback_repositories
1433
other_fb = other_repo._fallback_repositories
1434
if len(my_fb) != len(other_fb):
1436
for f, g in zip(my_fb, other_fb):
1437
if not f.has_same_location(g):
1441
def has_same_location(self, other):
1442
# TODO: Move to RepositoryBase and unify with the regular Repository
1443
# one; unfortunately the tests rely on slightly different behaviour at
1444
# present -- mbp 20090710
1445
return (self.__class__ is other.__class__ and
1446
self.bzrdir.transport.base == other.bzrdir.transport.base)
1448
def get_graph(self, other_repository=None):
1449
"""Return the graph for this repository format"""
1450
parents_provider = self._make_parents_provider(other_repository)
1451
return graph.Graph(parents_provider)
1454
def get_known_graph_ancestry(self, revision_ids):
1455
"""Return the known graph for a set of revision ids and their ancestors.
1457
st = static_tuple.StaticTuple
1458
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1459
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1460
return graph.GraphThunkIdsToKeys(known_graph)
1462
def gather_stats(self, revid=None, committers=None):
1463
"""See Repository.gather_stats()."""
1464
path = self.bzrdir._path_for_remote_call(self._client)
1465
# revid can be None to indicate no revisions, not just NULL_REVISION
1466
if revid is None or _mod_revision.is_null(revid):
1470
if committers is None or not committers:
1471
fmt_committers = 'no'
1473
fmt_committers = 'yes'
1474
response_tuple, response_handler = self._call_expecting_body(
1475
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1476
if response_tuple[0] != 'ok':
1477
raise errors.UnexpectedSmartServerResponse(response_tuple)
1479
body = response_handler.read_body_bytes()
1481
for line in body.split('\n'):
1484
key, val_text = line.split(':')
1485
if key in ('revisions', 'size', 'committers'):
1486
result[key] = int(val_text)
1487
elif key in ('firstrev', 'latestrev'):
1488
values = val_text.split(' ')[1:]
1489
result[key] = (float(values[0]), long(values[1]))
1493
def find_branches(self, using=False):
1494
"""See Repository.find_branches()."""
1495
# should be an API call to the server.
1497
return self._real_repository.find_branches(using=using)
1499
def get_physical_lock_status(self):
1500
"""See Repository.get_physical_lock_status()."""
1501
path = self.bzrdir._path_for_remote_call(self._client)
1503
response = self._call('Repository.get_physical_lock_status', path)
1504
except errors.UnknownSmartMethod:
1506
return self._real_repository.get_physical_lock_status()
1507
if response[0] not in ('yes', 'no'):
1508
raise errors.UnexpectedSmartServerResponse(response)
1509
return (response[0] == 'yes')
1511
def is_in_write_group(self):
1512
"""Return True if there is an open write group.
1514
write groups are only applicable locally for the smart server..
1516
if self._write_group_tokens is not None:
1518
if self._real_repository:
1519
return self._real_repository.is_in_write_group()
1521
def is_locked(self):
1522
return self._lock_count >= 1
1524
def is_shared(self):
1525
"""See Repository.is_shared()."""
1526
path = self.bzrdir._path_for_remote_call(self._client)
1527
response = self._call('Repository.is_shared', path)
1528
if response[0] not in ('yes', 'no'):
1529
raise SmartProtocolError('unexpected response code %s' % (response,))
1530
return response[0] == 'yes'
1532
def is_write_locked(self):
1533
return self._lock_mode == 'w'
1535
def _warn_if_deprecated(self, branch=None):
1536
# If we have a real repository, the check will be done there, if we
1537
# don't the check will be done remotely.
1540
def lock_read(self):
1541
"""Lock the repository for read operations.
1543
:return: A bzrlib.lock.LogicalLockResult.
1545
# wrong eventually - want a local lock cache context
1546
if not self._lock_mode:
1547
self._note_lock('r')
1548
self._lock_mode = 'r'
1549
self._lock_count = 1
1550
self._unstacked_provider.enable_cache(cache_misses=True)
1551
if self._real_repository is not None:
1552
self._real_repository.lock_read()
1553
for repo in self._fallback_repositories:
1556
self._lock_count += 1
1557
return lock.LogicalLockResult(self.unlock)
1559
def _remote_lock_write(self, token):
1560
path = self.bzrdir._path_for_remote_call(self._client)
1563
err_context = {'token': token}
1564
response = self._call('Repository.lock_write', path, token,
1566
if response[0] == 'ok':
1567
ok, token = response
1570
raise errors.UnexpectedSmartServerResponse(response)
1572
def lock_write(self, token=None, _skip_rpc=False):
1573
if not self._lock_mode:
1574
self._note_lock('w')
1576
if self._lock_token is not None:
1577
if token != self._lock_token:
1578
raise errors.TokenMismatch(token, self._lock_token)
1579
self._lock_token = token
1581
self._lock_token = self._remote_lock_write(token)
1582
# if self._lock_token is None, then this is something like packs or
1583
# svn where we don't get to lock the repo, or a weave style repository
1584
# where we cannot lock it over the wire and attempts to do so will
1586
if self._real_repository is not None:
1587
self._real_repository.lock_write(token=self._lock_token)
1588
if token is not None:
1589
self._leave_lock = True
1591
self._leave_lock = False
1592
self._lock_mode = 'w'
1593
self._lock_count = 1
1594
cache_misses = self._real_repository is None
1595
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1596
for repo in self._fallback_repositories:
1597
# Writes don't affect fallback repos
1599
elif self._lock_mode == 'r':
1600
raise errors.ReadOnlyError(self)
1602
self._lock_count += 1
1603
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1605
def leave_lock_in_place(self):
1606
if not self._lock_token:
1607
raise NotImplementedError(self.leave_lock_in_place)
1608
self._leave_lock = True
1610
def dont_leave_lock_in_place(self):
1611
if not self._lock_token:
1612
raise NotImplementedError(self.dont_leave_lock_in_place)
1613
self._leave_lock = False
1615
def _set_real_repository(self, repository):
1616
"""Set the _real_repository for this repository.
1618
:param repository: The repository to fallback to for non-hpss
1619
implemented operations.
1621
if self._real_repository is not None:
1622
# Replacing an already set real repository.
1623
# We cannot do this [currently] if the repository is locked -
1624
# synchronised state might be lost.
1625
if self.is_locked():
1626
raise AssertionError('_real_repository is already set')
1627
if isinstance(repository, RemoteRepository):
1628
raise AssertionError()
1629
self._real_repository = repository
1630
# three code paths happen here:
1631
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1632
# up stacking. In this case self._fallback_repositories is [], and the
1633
# real repo is already setup. Preserve the real repo and
1634
# RemoteRepository.add_fallback_repository will avoid adding
1636
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1637
# ensure_real is triggered from a branch, the real repository to
1638
# set already has a matching list with separate instances, but
1639
# as they are also RemoteRepositories we don't worry about making the
1640
# lists be identical.
1641
# 3) new servers, RemoteRepository.ensure_real is triggered before
1642
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1643
# and need to populate it.
1644
if (self._fallback_repositories and
1645
len(self._real_repository._fallback_repositories) !=
1646
len(self._fallback_repositories)):
1647
if len(self._real_repository._fallback_repositories):
1648
raise AssertionError(
1649
"cannot cleanly remove existing _fallback_repositories")
1650
for fb in self._fallback_repositories:
1651
self._real_repository.add_fallback_repository(fb)
1652
if self._lock_mode == 'w':
1653
# if we are already locked, the real repository must be able to
1654
# acquire the lock with our token.
1655
self._real_repository.lock_write(self._lock_token)
1656
elif self._lock_mode == 'r':
1657
self._real_repository.lock_read()
1658
if self._write_group_tokens is not None:
1659
# if we are already in a write group, resume it
1660
self._real_repository.resume_write_group(self._write_group_tokens)
1661
self._write_group_tokens = None
1663
def start_write_group(self):
1664
"""Start a write group on the decorated repository.
1666
Smart methods perform operations in a single step so this API
1667
is not really applicable except as a compatibility thunk
1668
for older plugins that don't use e.g. the CommitBuilder
1671
if self._real_repository:
1673
return self._real_repository.start_write_group()
1674
if not self.is_write_locked():
1675
raise errors.NotWriteLocked(self)
1676
if self._write_group_tokens is not None:
1677
raise errors.BzrError('already in a write group')
1678
path = self.bzrdir._path_for_remote_call(self._client)
1680
response = self._call('Repository.start_write_group', path,
1682
except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1684
return self._real_repository.start_write_group()
1685
if response[0] != 'ok':
1686
raise errors.UnexpectedSmartServerResponse(response)
1687
self._write_group_tokens = response[1]
1689
def _unlock(self, token):
1690
path = self.bzrdir._path_for_remote_call(self._client)
1692
# with no token the remote repository is not persistently locked.
1694
err_context = {'token': token}
1695
response = self._call('Repository.unlock', path, token,
1697
if response == ('ok',):
1700
raise errors.UnexpectedSmartServerResponse(response)
1702
@only_raises(errors.LockNotHeld, errors.LockBroken)
1704
if not self._lock_count:
1705
return lock.cant_unlock_not_held(self)
1706
self._lock_count -= 1
1707
if self._lock_count > 0:
1709
self._unstacked_provider.disable_cache()
1710
old_mode = self._lock_mode
1711
self._lock_mode = None
1713
# The real repository is responsible at present for raising an
1714
# exception if it's in an unfinished write group. However, it
1715
# normally will *not* actually remove the lock from disk - that's
1716
# done by the server on receiving the Repository.unlock call.
1717
# This is just to let the _real_repository stay up to date.
1718
if self._real_repository is not None:
1719
self._real_repository.unlock()
1720
elif self._write_group_tokens is not None:
1721
self.abort_write_group()
1723
# The rpc-level lock should be released even if there was a
1724
# problem releasing the vfs-based lock.
1726
# Only write-locked repositories need to make a remote method
1727
# call to perform the unlock.
1728
old_token = self._lock_token
1729
self._lock_token = None
1730
if not self._leave_lock:
1731
self._unlock(old_token)
1732
# Fallbacks are always 'lock_read()' so we don't pay attention to
1734
for repo in self._fallback_repositories:
1737
def break_lock(self):
1738
# should hand off to the network
1739
path = self.bzrdir._path_for_remote_call(self._client)
1741
response = self._call("Repository.break_lock", path)
1742
except errors.UnknownSmartMethod:
1744
return self._real_repository.break_lock()
1745
if response != ('ok',):
1746
raise errors.UnexpectedSmartServerResponse(response)
1748
def _get_tarball(self, compression):
1749
"""Return a TemporaryFile containing a repository tarball.
1751
Returns None if the server does not support sending tarballs.
1754
path = self.bzrdir._path_for_remote_call(self._client)
1756
response, protocol = self._call_expecting_body(
1757
'Repository.tarball', path, compression)
1758
except errors.UnknownSmartMethod:
1759
protocol.cancel_read_body()
1761
if response[0] == 'ok':
1762
# Extract the tarball and return it
1763
t = tempfile.NamedTemporaryFile()
1764
# TODO: rpc layer should read directly into it...
1765
t.write(protocol.read_body_bytes())
1768
raise errors.UnexpectedSmartServerResponse(response)
1771
def sprout(self, to_bzrdir, revision_id=None):
1772
"""Create a descendent repository for new development.
1774
Unlike clone, this does not copy the settings of the repository.
1776
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1777
dest_repo.fetch(self, revision_id=revision_id)
1780
def _create_sprouting_repo(self, a_bzrdir, shared):
1781
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
1782
# use target default format.
1783
dest_repo = a_bzrdir.create_repository()
1785
# Most control formats need the repository to be specifically
1786
# created, but on some old all-in-one formats it's not needed
1788
dest_repo = self._format.initialize(a_bzrdir, shared=shared)
1789
except errors.UninitializableFormat:
1790
dest_repo = a_bzrdir.open_repository()
1793
### These methods are just thin shims to the VFS object for now.
1796
def revision_tree(self, revision_id):
1797
revision_id = _mod_revision.ensure_null(revision_id)
1798
if revision_id == _mod_revision.NULL_REVISION:
1799
return InventoryRevisionTree(self,
1800
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1802
return list(self.revision_trees([revision_id]))[0]
1804
def get_serializer_format(self):
1805
path = self.bzrdir._path_for_remote_call(self._client)
1807
response = self._call('VersionedFileRepository.get_serializer_format',
1809
except errors.UnknownSmartMethod:
1811
return self._real_repository.get_serializer_format()
1812
if response[0] != 'ok':
1813
raise errors.UnexpectedSmartServerResponse(response)
1816
def get_commit_builder(self, branch, parents, config, timestamp=None,
1817
timezone=None, committer=None, revprops=None,
1818
revision_id=None, lossy=False):
1819
"""Obtain a CommitBuilder for this repository.
1821
:param branch: Branch to commit to.
1822
:param parents: Revision ids of the parents of the new revision.
1823
:param config: Configuration to use.
1824
:param timestamp: Optional timestamp recorded for commit.
1825
:param timezone: Optional timezone for timestamp.
1826
:param committer: Optional committer to set for commit.
1827
:param revprops: Optional dictionary of revision properties.
1828
:param revision_id: Optional revision id.
1829
:param lossy: Whether to discard data that can not be natively
1830
represented, when pushing to a foreign VCS
1832
if self._fallback_repositories and not self._format.supports_chks:
1833
raise errors.BzrError("Cannot commit directly to a stacked branch"
1834
" in pre-2a formats. See "
1835
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1836
if self._format.rich_root_data:
1837
commit_builder_kls = vf_repository.VersionedFileRootCommitBuilder
1839
commit_builder_kls = vf_repository.VersionedFileCommitBuilder
1840
result = commit_builder_kls(self, parents, config,
1841
timestamp, timezone, committer, revprops, revision_id,
1843
self.start_write_group()
1846
def add_fallback_repository(self, repository):
1847
"""Add a repository to use for looking up data not held locally.
1849
:param repository: A repository.
1851
if not self._format.supports_external_lookups:
1852
raise errors.UnstackableRepositoryFormat(
1853
self._format.network_name(), self.base)
1854
# We need to accumulate additional repositories here, to pass them in
1857
# Make the check before we lock: this raises an exception.
1858
self._check_fallback_repository(repository)
1859
if self.is_locked():
1860
# We will call fallback.unlock() when we transition to the unlocked
1861
# state, so always add a lock here. If a caller passes us a locked
1862
# repository, they are responsible for unlocking it later.
1863
repository.lock_read()
1864
self._fallback_repositories.append(repository)
1865
# If self._real_repository was parameterised already (e.g. because a
1866
# _real_branch had its get_stacked_on_url method called), then the
1867
# repository to be added may already be in the _real_repositories list.
1868
if self._real_repository is not None:
1869
fallback_locations = [repo.user_url for repo in
1870
self._real_repository._fallback_repositories]
1871
if repository.user_url not in fallback_locations:
1872
self._real_repository.add_fallback_repository(repository)
1874
def _check_fallback_repository(self, repository):
1875
"""Check that this repository can fallback to repository safely.
1877
Raise an error if not.
1879
:param repository: A repository to fallback to.
1881
return _mod_repository.InterRepository._assert_same_model(
1884
def add_inventory(self, revid, inv, parents):
1886
return self._real_repository.add_inventory(revid, inv, parents)
1888
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1889
parents, basis_inv=None, propagate_caches=False):
1891
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1892
delta, new_revision_id, parents, basis_inv=basis_inv,
1893
propagate_caches=propagate_caches)
1895
def add_revision(self, revision_id, rev, inv=None):
1896
_mod_revision.check_not_reserved_id(revision_id)
1897
key = (revision_id,)
1898
# check inventory present
1899
if not self.inventories.get_parent_map([key]):
1901
raise errors.WeaveRevisionNotPresent(revision_id,
1904
# yes, this is not suitable for adding with ghosts.
1905
rev.inventory_sha1 = self.add_inventory(revision_id, inv,
1908
rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
1909
self._add_revision(rev)
1911
def _add_revision(self, rev):
1912
if self._real_repository is not None:
1913
return self._real_repository._add_revision(rev)
1914
text = self._serializer.write_revision_to_string(rev)
1915
key = (rev.revision_id,)
1916
parents = tuple((parent,) for parent in rev.parent_ids)
1917
self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
1918
[('revisions', [FulltextContentFactory(key, parents, None, text)])],
1919
self._format, self._write_group_tokens)
1922
def get_inventory(self, revision_id):
1923
return list(self.iter_inventories([revision_id]))[0]
1925
def _iter_inventories_rpc(self, revision_ids, ordering):
1926
if ordering is None:
1927
ordering = 'unordered'
1928
path = self.bzrdir._path_for_remote_call(self._client)
1929
body = "\n".join(revision_ids)
1930
response_tuple, response_handler = (
1931
self._call_with_body_bytes_expecting_body(
1932
"VersionedFileRepository.get_inventories",
1933
(path, ordering), body))
1934
if response_tuple[0] != "ok":
1935
raise errors.UnexpectedSmartServerResponse(response_tuple)
1936
deserializer = inventory_delta.InventoryDeltaDeserializer()
1937
byte_stream = response_handler.read_streamed_body()
1938
decoded = smart_repo._byte_stream_to_stream(byte_stream)
1940
# no results whatsoever
1942
src_format, stream = decoded
1943
if src_format.network_name() != self._format.network_name():
1944
raise AssertionError(
1945
"Mismatched RemoteRepository and stream src %r, %r" % (
1946
src_format.network_name(), self._format.network_name()))
1947
# ignore the src format, it's not really relevant
1948
prev_inv = Inventory(root_id=None,
1949
revision_id=_mod_revision.NULL_REVISION)
1950
# there should be just one substream, with inventory deltas
1951
substream_kind, substream = stream.next()
1952
if substream_kind != "inventory-deltas":
1953
raise AssertionError(
1954
"Unexpected stream %r received" % substream_kind)
1955
for record in substream:
1956
(parent_id, new_id, versioned_root, tree_references, invdelta) = (
1957
deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
1958
if parent_id != prev_inv.revision_id:
1959
raise AssertionError("invalid base %r != %r" % (parent_id,
1960
prev_inv.revision_id))
1961
inv = prev_inv.create_by_apply_delta(invdelta, new_id)
1962
yield inv, inv.revision_id
1965
def _iter_inventories_vfs(self, revision_ids, ordering=None):
1967
return self._real_repository._iter_inventories(revision_ids, ordering)
1969
def iter_inventories(self, revision_ids, ordering=None):
1970
"""Get many inventories by revision_ids.
1972
This will buffer some or all of the texts used in constructing the
1973
inventories in memory, but will only parse a single inventory at a
1976
:param revision_ids: The expected revision ids of the inventories.
1977
:param ordering: optional ordering, e.g. 'topological'. If not
1978
specified, the order of revision_ids will be preserved (by
1979
buffering if necessary).
1980
:return: An iterator of inventories.
1982
if ((None in revision_ids)
1983
or (_mod_revision.NULL_REVISION in revision_ids)):
1984
raise ValueError('cannot get null revision inventory')
1985
for inv, revid in self._iter_inventories(revision_ids, ordering):
1987
raise errors.NoSuchRevision(self, revid)
1990
def _iter_inventories(self, revision_ids, ordering=None):
1991
if len(revision_ids) == 0:
1993
missing = set(revision_ids)
1994
if ordering is None:
1995
order_as_requested = True
1997
order = list(revision_ids)
1999
next_revid = order.pop()
2001
order_as_requested = False
2002
if ordering != 'unordered' and self._fallback_repositories:
2003
raise ValueError('unsupported ordering %r' % ordering)
2004
iter_inv_fns = [self._iter_inventories_rpc] + [
2005
fallback._iter_inventories for fallback in
2006
self._fallback_repositories]
2008
for iter_inv in iter_inv_fns:
2009
request = [revid for revid in revision_ids if revid in missing]
2010
for inv, revid in iter_inv(request, ordering):
2013
missing.remove(inv.revision_id)
2014
if ordering != 'unordered':
2018
if order_as_requested:
2019
# Yield as many results as we can while preserving order.
2020
while next_revid in invs:
2021
inv = invs.pop(next_revid)
2022
yield inv, inv.revision_id
2024
next_revid = order.pop()
2026
# We still want to fully consume the stream, just
2027
# in case it is not actually finished at this point
2030
except errors.UnknownSmartMethod:
2031
for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
2035
if order_as_requested:
2036
if next_revid is not None:
2037
yield None, next_revid
2040
yield invs.get(revid), revid
2043
yield None, missing.pop()
2046
def get_revision(self, revision_id):
2047
return self.get_revisions([revision_id])[0]
2049
def get_transaction(self):
2051
return self._real_repository.get_transaction()
2054
def clone(self, a_bzrdir, revision_id=None):
2055
dest_repo = self._create_sprouting_repo(
2056
a_bzrdir, shared=self.is_shared())
2057
self.copy_content_into(dest_repo, revision_id)
2060
def make_working_trees(self):
2061
"""See Repository.make_working_trees"""
2062
path = self.bzrdir._path_for_remote_call(self._client)
2064
response = self._call('Repository.make_working_trees', path)
2065
except errors.UnknownSmartMethod:
2067
return self._real_repository.make_working_trees()
2068
if response[0] not in ('yes', 'no'):
2069
raise SmartProtocolError('unexpected response code %s' % (response,))
2070
return response[0] == 'yes'
2072
def refresh_data(self):
2073
"""Re-read any data needed to synchronise with disk.
2075
This method is intended to be called after another repository instance
2076
(such as one used by a smart server) has inserted data into the
2077
repository. On all repositories this will work outside of write groups.
2078
Some repository formats (pack and newer for bzrlib native formats)
2079
support refresh_data inside write groups. If called inside a write
2080
group on a repository that does not support refreshing in a write group
2081
IsInWriteGroupError will be raised.
2083
if self._real_repository is not None:
2084
self._real_repository.refresh_data()
2085
# Refresh the parents cache for this object
2086
self._unstacked_provider.disable_cache()
2087
self._unstacked_provider.enable_cache()
2089
def revision_ids_to_search_result(self, result_set):
2090
"""Convert a set of revision ids to a graph SearchResult."""
2091
result_parents = set()
2092
for parents in self.get_graph().get_parent_map(
2093
result_set).itervalues():
2094
result_parents.update(parents)
2095
included_keys = result_set.intersection(result_parents)
2096
start_keys = result_set.difference(included_keys)
2097
exclude_keys = result_parents.difference(result_set)
2098
result = vf_search.SearchResult(start_keys, exclude_keys,
2099
len(result_set), result_set)
2103
def search_missing_revision_ids(self, other,
2104
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
2105
find_ghosts=True, revision_ids=None, if_present_ids=None,
2107
"""Return the revision ids that other has that this does not.
2109
These are returned in topological order.
2111
revision_id: only return revision ids included by revision_id.
2113
if symbol_versioning.deprecated_passed(revision_id):
2114
symbol_versioning.warn(
2115
'search_missing_revision_ids(revision_id=...) was '
2116
'deprecated in 2.4. Use revision_ids=[...] instead.',
2117
DeprecationWarning, stacklevel=2)
2118
if revision_ids is not None:
2119
raise AssertionError(
2120
'revision_ids is mutually exclusive with revision_id')
2121
if revision_id is not None:
2122
revision_ids = [revision_id]
2123
inter_repo = _mod_repository.InterRepository.get(other, self)
2124
return inter_repo.search_missing_revision_ids(
2125
find_ghosts=find_ghosts, revision_ids=revision_ids,
2126
if_present_ids=if_present_ids, limit=limit)
2128
def fetch(self, source, revision_id=None, find_ghosts=False,
2130
# No base implementation to use as RemoteRepository is not a subclass
2131
# of Repository; so this is a copy of Repository.fetch().
2132
if fetch_spec is not None and revision_id is not None:
2133
raise AssertionError(
2134
"fetch_spec and revision_id are mutually exclusive.")
2135
if self.is_in_write_group():
2136
raise errors.InternalBzrError(
2137
"May not fetch while in a write group.")
2138
# fast path same-url fetch operations
2139
if (self.has_same_location(source)
2140
and fetch_spec is None
2141
and self._has_same_fallbacks(source)):
2142
# check that last_revision is in 'from' and then return a
2144
if (revision_id is not None and
2145
not _mod_revision.is_null(revision_id)):
2146
self.get_revision(revision_id)
2148
# if there is no specific appropriate InterRepository, this will get
2149
# the InterRepository base class, which raises an
2150
# IncompatibleRepositories when asked to fetch.
2151
inter = _mod_repository.InterRepository.get(source, self)
2152
if (fetch_spec is not None and
2153
not getattr(inter, "supports_fetch_spec", False)):
2154
raise errors.UnsupportedOperation(
2155
"fetch_spec not supported for %r" % inter)
2156
return inter.fetch(revision_id=revision_id,
2157
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
2159
def create_bundle(self, target, base, fileobj, format=None):
2161
self._real_repository.create_bundle(target, base, fileobj, format)
2164
@symbol_versioning.deprecated_method(
2165
symbol_versioning.deprecated_in((2, 4, 0)))
2166
def get_ancestry(self, revision_id, topo_sorted=True):
2168
return self._real_repository.get_ancestry(revision_id, topo_sorted)
2170
def fileids_altered_by_revision_ids(self, revision_ids):
2172
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2174
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
2176
return self._real_repository._get_versioned_file_checker(
2177
revisions, revision_versions_cache)
2179
def _iter_files_bytes_rpc(self, desired_files, absent):
2180
path = self.bzrdir._path_for_remote_call(self._client)
2183
for (file_id, revid, identifier) in desired_files:
2184
lines.append("%s\0%s" % (
2185
osutils.safe_file_id(file_id),
2186
osutils.safe_revision_id(revid)))
2187
identifiers.append(identifier)
2188
(response_tuple, response_handler) = (
2189
self._call_with_body_bytes_expecting_body(
2190
"Repository.iter_files_bytes", (path, ), "\n".join(lines)))
2191
if response_tuple != ('ok', ):
2192
response_handler.cancel_read_body()
2193
raise errors.UnexpectedSmartServerResponse(response_tuple)
2194
byte_stream = response_handler.read_streamed_body()
2195
def decompress_stream(start, byte_stream, unused):
2196
decompressor = zlib.decompressobj()
2197
yield decompressor.decompress(start)
2198
while decompressor.unused_data == "":
2200
data = byte_stream.next()
2201
except StopIteration:
2203
yield decompressor.decompress(data)
2204
yield decompressor.flush()
2205
unused.append(decompressor.unused_data)
2208
while not "\n" in unused:
2209
unused += byte_stream.next()
2210
header, rest = unused.split("\n", 1)
2211
args = header.split("\0")
2212
if args[0] == "absent":
2213
absent[identifiers[int(args[3])]] = (args[1], args[2])
2216
elif args[0] == "ok":
2219
raise errors.UnexpectedSmartServerResponse(args)
2221
yield (identifiers[idx],
2222
decompress_stream(rest, byte_stream, unused_chunks))
2223
unused = "".join(unused_chunks)
2225
def iter_files_bytes(self, desired_files):
2226
"""See Repository.iter_file_bytes.
2230
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2231
desired_files, absent):
2232
yield identifier, bytes_iterator
2233
for fallback in self._fallback_repositories:
2236
desired_files = [(key[0], key[1], identifier) for
2237
(identifier, key) in absent.iteritems()]
2238
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2239
del absent[identifier]
2240
yield identifier, bytes_iterator
2242
# There may be more missing items, but raise an exception
2244
missing_identifier = absent.keys()[0]
2245
missing_key = absent[missing_identifier]
2246
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2247
file_id=missing_key[0])
2248
except errors.UnknownSmartMethod:
2250
for (identifier, bytes_iterator) in (
2251
self._real_repository.iter_files_bytes(desired_files)):
2252
yield identifier, bytes_iterator
2254
def get_cached_parent_map(self, revision_ids):
2255
"""See bzrlib.CachingParentsProvider.get_cached_parent_map"""
2256
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2258
def get_parent_map(self, revision_ids):
2259
"""See bzrlib.Graph.get_parent_map()."""
2260
return self._make_parents_provider().get_parent_map(revision_ids)
2262
def _get_parent_map_rpc(self, keys):
2263
"""Helper for get_parent_map that performs the RPC."""
2264
medium = self._client._medium
2265
if medium._is_remote_before((1, 2)):
2266
# We already found out that the server can't understand
2267
# Repository.get_parent_map requests, so just fetch the whole
2270
# Note that this reads the whole graph, when only some keys are
2271
# wanted. On this old server there's no way (?) to get them all
2272
# in one go, and the user probably will have seen a warning about
2273
# the server being old anyhow.
2274
rg = self._get_revision_graph(None)
2275
# There is an API discrepancy between get_parent_map and
2276
# get_revision_graph. Specifically, a "key:()" pair in
2277
# get_revision_graph just means a node has no parents. For
2278
# "get_parent_map" it means the node is a ghost. So fix up the
2279
# graph to correct this.
2280
# https://bugs.launchpad.net/bzr/+bug/214894
2281
# There is one other "bug" which is that ghosts in
2282
# get_revision_graph() are not returned at all. But we won't worry
2283
# about that for now.
2284
for node_id, parent_ids in rg.iteritems():
2285
if parent_ids == ():
2286
rg[node_id] = (NULL_REVISION,)
2287
rg[NULL_REVISION] = ()
2292
raise ValueError('get_parent_map(None) is not valid')
2293
if NULL_REVISION in keys:
2294
keys.discard(NULL_REVISION)
2295
found_parents = {NULL_REVISION:()}
2297
return found_parents
2300
# TODO(Needs analysis): We could assume that the keys being requested
2301
# from get_parent_map are in a breadth first search, so typically they
2302
# will all be depth N from some common parent, and we don't have to
2303
# have the server iterate from the root parent, but rather from the
2304
# keys we're searching; and just tell the server the keyspace we
2305
# already have; but this may be more traffic again.
2307
# Transform self._parents_map into a search request recipe.
2308
# TODO: Manage this incrementally to avoid covering the same path
2309
# repeatedly. (The server will have to on each request, but the less
2310
# work done the better).
2312
# Negative caching notes:
2313
# new server sends missing when a request including the revid
2314
# 'include-missing:' is present in the request.
2315
# missing keys are serialised as missing:X, and we then call
2316
# provider.note_missing(X) for-all X
2317
parents_map = self._unstacked_provider.get_cached_map()
2318
if parents_map is None:
2319
# Repository is not locked, so there's no cache.
2321
if _DEFAULT_SEARCH_DEPTH <= 0:
2322
(start_set, stop_keys,
2323
key_count) = vf_search.search_result_from_parent_map(
2324
parents_map, self._unstacked_provider.missing_keys)
2326
(start_set, stop_keys,
2327
key_count) = vf_search.limited_search_result_from_parent_map(
2328
parents_map, self._unstacked_provider.missing_keys,
2329
keys, depth=_DEFAULT_SEARCH_DEPTH)
2330
recipe = ('manual', start_set, stop_keys, key_count)
2331
body = self._serialise_search_recipe(recipe)
2332
path = self.bzrdir._path_for_remote_call(self._client)
2334
if type(key) is not str:
2336
"key %r not a plain string" % (key,))
2337
verb = 'Repository.get_parent_map'
2338
args = (path, 'include-missing:') + tuple(keys)
2340
response = self._call_with_body_bytes_expecting_body(
2342
except errors.UnknownSmartMethod:
2343
# Server does not support this method, so get the whole graph.
2344
# Worse, we have to force a disconnection, because the server now
2345
# doesn't realise it has a body on the wire to consume, so the
2346
# only way to recover is to abandon the connection.
2348
'Server is too old for fast get_parent_map, reconnecting. '
2349
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2351
# To avoid having to disconnect repeatedly, we keep track of the
2352
# fact the server doesn't understand remote methods added in 1.2.
2353
medium._remember_remote_is_before((1, 2))
2354
# Recurse just once and we should use the fallback code.
2355
return self._get_parent_map_rpc(keys)
2356
response_tuple, response_handler = response
2357
if response_tuple[0] not in ['ok']:
2358
response_handler.cancel_read_body()
2359
raise errors.UnexpectedSmartServerResponse(response_tuple)
2360
if response_tuple[0] == 'ok':
2361
coded = bz2.decompress(response_handler.read_body_bytes())
2363
# no revisions found
2365
lines = coded.split('\n')
2368
d = tuple(line.split())
2370
revision_graph[d[0]] = d[1:]
2373
if d[0].startswith('missing:'):
2375
self._unstacked_provider.note_missing_key(revid)
2377
# no parents - so give the Graph result
2379
revision_graph[d[0]] = (NULL_REVISION,)
2380
return revision_graph
2383
def get_signature_text(self, revision_id):
2384
path = self.bzrdir._path_for_remote_call(self._client)
2386
response_tuple, response_handler = self._call_expecting_body(
2387
'Repository.get_revision_signature_text', path, revision_id)
2388
except errors.UnknownSmartMethod:
2390
return self._real_repository.get_signature_text(revision_id)
2391
except errors.NoSuchRevision, err:
2392
for fallback in self._fallback_repositories:
2394
return fallback.get_signature_text(revision_id)
2395
except errors.NoSuchRevision:
2399
if response_tuple[0] != 'ok':
2400
raise errors.UnexpectedSmartServerResponse(response_tuple)
2401
return response_handler.read_body_bytes()
2404
def _get_inventory_xml(self, revision_id):
2405
# This call is used by older working tree formats,
2406
# which stored a serialized basis inventory.
2408
return self._real_repository._get_inventory_xml(revision_id)
2411
def reconcile(self, other=None, thorough=False):
2412
from bzrlib.reconcile import RepoReconciler
2413
path = self.bzrdir._path_for_remote_call(self._client)
2415
response, handler = self._call_expecting_body(
2416
'Repository.reconcile', path, self._lock_token)
2417
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2419
return self._real_repository.reconcile(other=other, thorough=thorough)
2420
if response != ('ok', ):
2421
raise errors.UnexpectedSmartServerResponse(response)
2422
body = handler.read_body_bytes()
2423
result = RepoReconciler(self)
2424
for line in body.split('\n'):
2427
key, val_text = line.split(':')
2428
if key == "garbage_inventories":
2429
result.garbage_inventories = int(val_text)
2430
elif key == "inconsistent_parents":
2431
result.inconsistent_parents = int(val_text)
2433
mutter("unknown reconcile key %r" % key)
2436
def all_revision_ids(self):
2437
path = self.bzrdir._path_for_remote_call(self._client)
2439
response_tuple, response_handler = self._call_expecting_body(
2440
"Repository.all_revision_ids", path)
2441
except errors.UnknownSmartMethod:
2443
return self._real_repository.all_revision_ids()
2444
if response_tuple != ("ok", ):
2445
raise errors.UnexpectedSmartServerResponse(response_tuple)
2446
revids = set(response_handler.read_body_bytes().splitlines())
2447
for fallback in self._fallback_repositories:
2448
revids.update(set(fallback.all_revision_ids()))
2451
def _filtered_revision_trees(self, revision_ids, file_ids):
2452
"""Return Tree for a revision on this branch with only some files.
2454
:param revision_ids: a sequence of revision-ids;
2455
a revision-id may not be None or 'null:'
2456
:param file_ids: if not None, the result is filtered
2457
so that only those file-ids, their parents and their
2458
children are included.
2460
inventories = self.iter_inventories(revision_ids)
2461
for inv in inventories:
2462
# Should we introduce a FilteredRevisionTree class rather
2463
# than pre-filter the inventory here?
2464
filtered_inv = inv.filter(file_ids)
2465
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2468
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2469
medium = self._client._medium
2470
if medium._is_remote_before((1, 2)):
2472
for delta in self._real_repository.get_deltas_for_revisions(
2473
revisions, specific_fileids):
2476
# Get the revision-ids of interest
2477
required_trees = set()
2478
for revision in revisions:
2479
required_trees.add(revision.revision_id)
2480
required_trees.update(revision.parent_ids[:1])
2482
# Get the matching filtered trees. Note that it's more
2483
# efficient to pass filtered trees to changes_from() rather
2484
# than doing the filtering afterwards. changes_from() could
2485
# arguably do the filtering itself but it's path-based, not
2486
# file-id based, so filtering before or afterwards is
2488
if specific_fileids is None:
2489
trees = dict((t.get_revision_id(), t) for
2490
t in self.revision_trees(required_trees))
2492
trees = dict((t.get_revision_id(), t) for
2493
t in self._filtered_revision_trees(required_trees,
2496
# Calculate the deltas
2497
for revision in revisions:
2498
if not revision.parent_ids:
2499
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2501
old_tree = trees[revision.parent_ids[0]]
2502
yield trees[revision.revision_id].changes_from(old_tree)
2505
def get_revision_delta(self, revision_id, specific_fileids=None):
2506
r = self.get_revision(revision_id)
2507
return list(self.get_deltas_for_revisions([r],
2508
specific_fileids=specific_fileids))[0]
2511
def revision_trees(self, revision_ids):
2512
inventories = self.iter_inventories(revision_ids)
2513
for inv in inventories:
2514
yield InventoryRevisionTree(self, inv, inv.revision_id)
2517
def get_revision_reconcile(self, revision_id):
2519
return self._real_repository.get_revision_reconcile(revision_id)
2522
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
2524
return self._real_repository.check(revision_ids=revision_ids,
2525
callback_refs=callback_refs, check_repo=check_repo)
2527
def copy_content_into(self, destination, revision_id=None):
2528
"""Make a complete copy of the content in self into destination.
2530
This is a destructive operation! Do not use it on existing
2533
interrepo = _mod_repository.InterRepository.get(self, destination)
2534
return interrepo.copy_content(revision_id)
2536
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
2537
# get a tarball of the remote repository, and copy from that into the
2540
# TODO: Maybe a progress bar while streaming the tarball?
2541
note(gettext("Copying repository content as tarball..."))
2542
tar_file = self._get_tarball('bz2')
2543
if tar_file is None:
2545
destination = to_bzrdir.create_repository()
2547
tar = tarfile.open('repository', fileobj=tar_file,
2549
tmpdir = osutils.mkdtemp()
2551
_extract_tar(tar, tmpdir)
2552
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
2553
tmp_repo = tmp_bzrdir.open_repository()
2554
tmp_repo.copy_content_into(destination, revision_id)
2556
osutils.rmtree(tmpdir)
2560
# TODO: Suggestion from john: using external tar is much faster than
2561
# python's tarfile library, but it may not work on windows.
2564
def inventories(self):
2565
"""Decorate the real repository for now.
2567
In the long term a full blown network facility is needed to
2568
avoid creating a real repository object locally.
2571
return self._real_repository.inventories
2574
def pack(self, hint=None, clean_obsolete_packs=False):
2575
"""Compress the data within the repository.
2580
body = "".join([l+"\n" for l in hint])
2581
path = self.bzrdir._path_for_remote_call(self._client)
2583
response, handler = self._call_with_body_bytes_expecting_body(
2584
'Repository.pack', (path, self._lock_token,
2585
str(clean_obsolete_packs)), body)
2586
except errors.UnknownSmartMethod:
2588
return self._real_repository.pack(hint=hint,
2589
clean_obsolete_packs=clean_obsolete_packs)
2590
handler.cancel_read_body()
2591
if response != ('ok', ):
2592
raise errors.UnexpectedSmartServerResponse(response)
2595
def revisions(self):
2596
"""Decorate the real repository for now.
2598
In the long term a full blown network facility is needed.
2601
return self._real_repository.revisions
2603
def set_make_working_trees(self, new_value):
2605
new_value_str = "True"
2607
new_value_str = "False"
2608
path = self.bzrdir._path_for_remote_call(self._client)
2610
response = self._call(
2611
'Repository.set_make_working_trees', path, new_value_str)
2612
except errors.UnknownSmartMethod:
2614
self._real_repository.set_make_working_trees(new_value)
2616
if response[0] != 'ok':
2617
raise errors.UnexpectedSmartServerResponse(response)
2620
def signatures(self):
2621
"""Decorate the real repository for now.
2623
In the long term a full blown network facility is needed to avoid
2624
creating a real repository object locally.
2627
return self._real_repository.signatures
2630
def sign_revision(self, revision_id, gpg_strategy):
2631
testament = _mod_testament.Testament.from_revision(self, revision_id)
2632
plaintext = testament.as_short_text()
2633
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2637
"""Decorate the real repository for now.
2639
In the long term a full blown network facility is needed to avoid
2640
creating a real repository object locally.
2643
return self._real_repository.texts
2645
def _iter_revisions_rpc(self, revision_ids):
2646
body = "\n".join(revision_ids)
2647
path = self.bzrdir._path_for_remote_call(self._client)
2648
response_tuple, response_handler = (
2649
self._call_with_body_bytes_expecting_body(
2650
"Repository.iter_revisions", (path, ), body))
2651
if response_tuple[0] != "ok":
2652
raise errors.UnexpectedSmartServerResponse(response_tuple)
2653
serializer_format = response_tuple[1]
2654
serializer = serializer_format_registry.get(serializer_format)
2655
byte_stream = response_handler.read_streamed_body()
2656
decompressor = zlib.decompressobj()
2658
for bytes in byte_stream:
2659
chunks.append(decompressor.decompress(bytes))
2660
if decompressor.unused_data != "":
2661
chunks.append(decompressor.flush())
2662
yield serializer.read_revision_from_string("".join(chunks))
2663
unused = decompressor.unused_data
2664
decompressor = zlib.decompressobj()
2665
chunks = [decompressor.decompress(unused)]
2666
chunks.append(decompressor.flush())
2667
text = "".join(chunks)
2669
yield serializer.read_revision_from_string("".join(chunks))
2672
def get_revisions(self, revision_ids):
2673
if revision_ids is None:
2674
revision_ids = self.all_revision_ids()
2676
for rev_id in revision_ids:
2677
if not rev_id or not isinstance(rev_id, basestring):
2678
raise errors.InvalidRevisionId(
2679
revision_id=rev_id, branch=self)
2681
missing = set(revision_ids)
2683
for rev in self._iter_revisions_rpc(revision_ids):
2684
missing.remove(rev.revision_id)
2685
revs[rev.revision_id] = rev
2686
except errors.UnknownSmartMethod:
2688
return self._real_repository.get_revisions(revision_ids)
2689
for fallback in self._fallback_repositories:
2692
for revid in list(missing):
2693
# XXX JRV 2011-11-20: It would be nice if there was a
2694
# public method on Repository that could be used to query
2695
# for revision objects *without* failing completely if one
2696
# was missing. There is VersionedFileRepository._iter_revisions,
2697
# but unfortunately that's private and not provided by
2698
# all repository implementations.
2700
revs[revid] = fallback.get_revision(revid)
2701
except errors.NoSuchRevision:
2704
missing.remove(revid)
2706
raise errors.NoSuchRevision(self, list(missing)[0])
2707
return [revs[revid] for revid in revision_ids]
2709
def supports_rich_root(self):
2710
return self._format.rich_root_data
2712
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2713
def iter_reverse_revision_history(self, revision_id):
2715
return self._real_repository.iter_reverse_revision_history(revision_id)
2718
def _serializer(self):
2719
return self._format._serializer
2722
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2723
signature = gpg_strategy.sign(plaintext)
2724
self.add_signature_text(revision_id, signature)
2726
def add_signature_text(self, revision_id, signature):
2727
if self._real_repository:
2728
# If there is a real repository the write group will
2729
# be in the real repository as well, so use that:
2731
return self._real_repository.add_signature_text(
2732
revision_id, signature)
2733
path = self.bzrdir._path_for_remote_call(self._client)
2734
response, handler = self._call_with_body_bytes_expecting_body(
2735
'Repository.add_signature_text', (path, self._lock_token,
2736
revision_id) + tuple(self._write_group_tokens), signature)
2737
handler.cancel_read_body()
2739
if response[0] != 'ok':
2740
raise errors.UnexpectedSmartServerResponse(response)
2741
self._write_group_tokens = response[1:]
2743
def has_signature_for_revision_id(self, revision_id):
2744
path = self.bzrdir._path_for_remote_call(self._client)
2746
response = self._call('Repository.has_signature_for_revision_id',
2748
except errors.UnknownSmartMethod:
2750
return self._real_repository.has_signature_for_revision_id(
2752
if response[0] not in ('yes', 'no'):
2753
raise SmartProtocolError('unexpected response code %s' % (response,))
2754
if response[0] == 'yes':
2756
for fallback in self._fallback_repositories:
2757
if fallback.has_signature_for_revision_id(revision_id):
2762
def verify_revision_signature(self, revision_id, gpg_strategy):
2763
if not self.has_signature_for_revision_id(revision_id):
2764
return gpg.SIGNATURE_NOT_SIGNED, None
2765
signature = self.get_signature_text(revision_id)
2767
testament = _mod_testament.Testament.from_revision(self, revision_id)
2768
plaintext = testament.as_short_text()
2770
return gpg_strategy.verify(signature, plaintext)
2772
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2774
return self._real_repository.item_keys_introduced_by(revision_ids,
2775
_files_pb=_files_pb)
2777
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2779
return self._real_repository._find_inconsistent_revision_parents(
2782
def _check_for_inconsistent_revision_parents(self):
2784
return self._real_repository._check_for_inconsistent_revision_parents()
2786
def _make_parents_provider(self, other=None):
2787
providers = [self._unstacked_provider]
2788
if other is not None:
2789
providers.insert(0, other)
2790
return graph.StackedParentsProvider(_LazyListJoin(
2791
providers, self._fallback_repositories))
2793
def _serialise_search_recipe(self, recipe):
2794
"""Serialise a graph search recipe.
2796
:param recipe: A search recipe (start, stop, count).
2797
:return: Serialised bytes.
2799
start_keys = ' '.join(recipe[1])
2800
stop_keys = ' '.join(recipe[2])
2801
count = str(recipe[3])
2802
return '\n'.join((start_keys, stop_keys, count))
2804
def _serialise_search_result(self, search_result):
2805
parts = search_result.get_network_struct()
2806
return '\n'.join(parts)
2809
path = self.bzrdir._path_for_remote_call(self._client)
2811
response = self._call('PackRepository.autopack', path)
2812
except errors.UnknownSmartMethod:
2814
self._real_repository._pack_collection.autopack()
2817
if response[0] != 'ok':
2818
raise errors.UnexpectedSmartServerResponse(response)
2821
class RemoteStreamSink(vf_repository.StreamSink):
2823
def _insert_real(self, stream, src_format, resume_tokens):
2824
self.target_repo._ensure_real()
2825
sink = self.target_repo._real_repository._get_sink()
2826
result = sink.insert_stream(stream, src_format, resume_tokens)
2828
self.target_repo.autopack()
2831
def insert_stream(self, stream, src_format, resume_tokens):
2832
target = self.target_repo
2833
target._unstacked_provider.missing_keys.clear()
2834
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2835
if target._lock_token:
2836
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2837
lock_args = (target._lock_token or '',)
2839
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2841
client = target._client
2842
medium = client._medium
2843
path = target.bzrdir._path_for_remote_call(client)
2844
# Probe for the verb to use with an empty stream before sending the
2845
# real stream to it. We do this both to avoid the risk of sending a
2846
# large request that is then rejected, and because we don't want to
2847
# implement a way to buffer, rewind, or restart the stream.
2849
for verb, required_version in candidate_calls:
2850
if medium._is_remote_before(required_version):
2853
# We've already done the probing (and set _is_remote_before) on
2854
# a previous insert.
2857
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2859
response = client.call_with_body_stream(
2860
(verb, path, '') + lock_args, byte_stream)
2861
except errors.UnknownSmartMethod:
2862
medium._remember_remote_is_before(required_version)
2868
return self._insert_real(stream, src_format, resume_tokens)
2869
self._last_inv_record = None
2870
self._last_substream = None
2871
if required_version < (1, 19):
2872
# Remote side doesn't support inventory deltas. Wrap the stream to
2873
# make sure we don't send any. If the stream contains inventory
2874
# deltas we'll interrupt the smart insert_stream request and
2876
stream = self._stop_stream_if_inventory_delta(stream)
2877
byte_stream = smart_repo._stream_to_byte_stream(
2879
resume_tokens = ' '.join(resume_tokens)
2880
response = client.call_with_body_stream(
2881
(verb, path, resume_tokens) + lock_args, byte_stream)
2882
if response[0][0] not in ('ok', 'missing-basis'):
2883
raise errors.UnexpectedSmartServerResponse(response)
2884
if self._last_substream is not None:
2885
# The stream included an inventory-delta record, but the remote
2886
# side isn't new enough to support them. So we need to send the
2887
# rest of the stream via VFS.
2888
self.target_repo.refresh_data()
2889
return self._resume_stream_with_vfs(response, src_format)
2890
if response[0][0] == 'missing-basis':
2891
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2892
resume_tokens = tokens
2893
return resume_tokens, set(missing_keys)
2895
self.target_repo.refresh_data()
2898
def _resume_stream_with_vfs(self, response, src_format):
2899
"""Resume sending a stream via VFS, first resending the record and
2900
substream that couldn't be sent via an insert_stream verb.
2902
if response[0][0] == 'missing-basis':
2903
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2904
# Ignore missing_keys, we haven't finished inserting yet
2907
def resume_substream():
2908
# Yield the substream that was interrupted.
2909
for record in self._last_substream:
2911
self._last_substream = None
2912
def resume_stream():
2913
# Finish sending the interrupted substream
2914
yield ('inventory-deltas', resume_substream())
2915
# Then simply continue sending the rest of the stream.
2916
for substream_kind, substream in self._last_stream:
2917
yield substream_kind, substream
2918
return self._insert_real(resume_stream(), src_format, tokens)
2920
def _stop_stream_if_inventory_delta(self, stream):
2921
"""Normally this just lets the original stream pass-through unchanged.
2923
However if any 'inventory-deltas' substream occurs it will stop
2924
streaming, and store the interrupted substream and stream in
2925
self._last_substream and self._last_stream so that the stream can be
2926
resumed by _resume_stream_with_vfs.
2929
stream_iter = iter(stream)
2930
for substream_kind, substream in stream_iter:
2931
if substream_kind == 'inventory-deltas':
2932
self._last_substream = substream
2933
self._last_stream = stream_iter
2936
yield substream_kind, substream
2939
class RemoteStreamSource(vf_repository.StreamSource):
2940
"""Stream data from a remote server."""
2942
def get_stream(self, search):
2943
if (self.from_repository._fallback_repositories and
2944
self.to_format._fetch_order == 'topological'):
2945
return self._real_stream(self.from_repository, search)
2948
repos = [self.from_repository]
2954
repos.extend(repo._fallback_repositories)
2955
sources.append(repo)
2956
return self.missing_parents_chain(search, sources)
2958
def get_stream_for_missing_keys(self, missing_keys):
2959
self.from_repository._ensure_real()
2960
real_repo = self.from_repository._real_repository
2961
real_source = real_repo._get_source(self.to_format)
2962
return real_source.get_stream_for_missing_keys(missing_keys)
2964
def _real_stream(self, repo, search):
2965
"""Get a stream for search from repo.
2967
This never called RemoteStreamSource.get_stream, and is a helper
2968
for RemoteStreamSource._get_stream to allow getting a stream
2969
reliably whether fallback back because of old servers or trying
2970
to stream from a non-RemoteRepository (which the stacked support
2973
source = repo._get_source(self.to_format)
2974
if isinstance(source, RemoteStreamSource):
2976
source = repo._real_repository._get_source(self.to_format)
2977
return source.get_stream(search)
2979
def _get_stream(self, repo, search):
2980
"""Core worker to get a stream from repo for search.
2982
This is used by both get_stream and the stacking support logic. It
2983
deliberately gets a stream for repo which does not need to be
2984
self.from_repository. In the event that repo is not Remote, or
2985
cannot do a smart stream, a fallback is made to the generic
2986
repository._get_stream() interface, via self._real_stream.
2988
In the event of stacking, streams from _get_stream will not
2989
contain all the data for search - this is normal (see get_stream).
2991
:param repo: A repository.
2992
:param search: A search.
2994
# Fallbacks may be non-smart
2995
if not isinstance(repo, RemoteRepository):
2996
return self._real_stream(repo, search)
2997
client = repo._client
2998
medium = client._medium
2999
path = repo.bzrdir._path_for_remote_call(client)
3000
search_bytes = repo._serialise_search_result(search)
3001
args = (path, self.to_format.network_name())
3003
('Repository.get_stream_1.19', (1, 19)),
3004
('Repository.get_stream', (1, 13))]
3007
for verb, version in candidate_verbs:
3008
if medium._is_remote_before(version):
3011
response = repo._call_with_body_bytes_expecting_body(
3012
verb, args, search_bytes)
3013
except errors.UnknownSmartMethod:
3014
medium._remember_remote_is_before(version)
3015
except errors.UnknownErrorFromSmartServer, e:
3016
if isinstance(search, vf_search.EverythingResult):
3017
error_verb = e.error_from_smart_server.error_verb
3018
if error_verb == 'BadSearch':
3019
# Pre-2.4 servers don't support this sort of search.
3020
# XXX: perhaps falling back to VFS on BadSearch is a
3021
# good idea in general? It might provide a little bit
3022
# of protection against client-side bugs.
3023
medium._remember_remote_is_before((2, 4))
3027
response_tuple, response_handler = response
3031
return self._real_stream(repo, search)
3032
if response_tuple[0] != 'ok':
3033
raise errors.UnexpectedSmartServerResponse(response_tuple)
3034
byte_stream = response_handler.read_streamed_body()
3035
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3036
self._record_counter)
3037
if src_format.network_name() != repo._format.network_name():
3038
raise AssertionError(
3039
"Mismatched RemoteRepository and stream src %r, %r" % (
3040
src_format.network_name(), repo._format.network_name()))
3043
def missing_parents_chain(self, search, sources):
3044
"""Chain multiple streams together to handle stacking.
3046
:param search: The overall search to satisfy with streams.
3047
:param sources: A list of Repository objects to query.
3049
self.from_serialiser = self.from_repository._format._serializer
3050
self.seen_revs = set()
3051
self.referenced_revs = set()
3052
# If there are heads in the search, or the key count is > 0, we are not
3054
while not search.is_empty() and len(sources) > 1:
3055
source = sources.pop(0)
3056
stream = self._get_stream(source, search)
3057
for kind, substream in stream:
3058
if kind != 'revisions':
3059
yield kind, substream
3061
yield kind, self.missing_parents_rev_handler(substream)
3062
search = search.refine(self.seen_revs, self.referenced_revs)
3063
self.seen_revs = set()
3064
self.referenced_revs = set()
3065
if not search.is_empty():
3066
for kind, stream in self._get_stream(sources[0], search):
3069
def missing_parents_rev_handler(self, substream):
3070
for content in substream:
3071
revision_bytes = content.get_bytes_as('fulltext')
3072
revision = self.from_serialiser.read_revision_from_string(
3074
self.seen_revs.add(content.key[-1])
3075
self.referenced_revs.update(revision.parent_ids)
3079
class RemoteBranchLockableFiles(LockableFiles):
3080
"""A 'LockableFiles' implementation that talks to a smart server.
3082
This is not a public interface class.
3085
def __init__(self, bzrdir, _client):
3086
self.bzrdir = bzrdir
3087
self._client = _client
3088
self._need_find_modes = True
3089
LockableFiles.__init__(
3090
self, bzrdir.get_branch_transport(None),
3091
'lock', lockdir.LockDir)
3093
def _find_modes(self):
3094
# RemoteBranches don't let the client set the mode of control files.
3095
self._dir_mode = None
3096
self._file_mode = None
3099
class RemoteBranchFormat(branch.BranchFormat):
3101
def __init__(self, network_name=None):
3102
super(RemoteBranchFormat, self).__init__()
3103
self._matchingbzrdir = RemoteBzrDirFormat()
3104
self._matchingbzrdir.set_branch_format(self)
3105
self._custom_format = None
3106
self._network_name = network_name
3108
def __eq__(self, other):
3109
return (isinstance(other, RemoteBranchFormat) and
3110
self.__dict__ == other.__dict__)
3112
def _ensure_real(self):
3113
if self._custom_format is None:
3115
self._custom_format = branch.network_format_registry.get(
3118
raise errors.UnknownFormatError(kind='branch',
3119
format=self._network_name)
3121
def get_format_description(self):
3123
return 'Remote: ' + self._custom_format.get_format_description()
3125
def network_name(self):
3126
return self._network_name
3128
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
3129
return a_bzrdir.open_branch(name=name,
3130
ignore_fallbacks=ignore_fallbacks)
3132
def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
3133
# Initialisation when using a local bzrdir object, or a non-vfs init
3134
# method is not available on the server.
3135
# self._custom_format is always set - the start of initialize ensures
3137
if isinstance(a_bzrdir, RemoteBzrDir):
3138
a_bzrdir._ensure_real()
3139
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
3140
name=name, append_revisions_only=append_revisions_only)
3142
# We assume the bzrdir is parameterised; it may not be.
3143
result = self._custom_format.initialize(a_bzrdir, name=name,
3144
append_revisions_only=append_revisions_only)
3145
if (isinstance(a_bzrdir, RemoteBzrDir) and
3146
not isinstance(result, RemoteBranch)):
3147
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3151
def initialize(self, a_bzrdir, name=None, repository=None,
3152
append_revisions_only=None):
3154
name = a_bzrdir._get_selected_branch()
3155
# 1) get the network name to use.
3156
if self._custom_format:
3157
network_name = self._custom_format.network_name()
3159
# Select the current bzrlib default and ask for that.
3160
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
3161
reference_format = reference_bzrdir_format.get_branch_format()
3162
self._custom_format = reference_format
3163
network_name = reference_format.network_name()
3164
# Being asked to create on a non RemoteBzrDir:
3165
if not isinstance(a_bzrdir, RemoteBzrDir):
3166
return self._vfs_initialize(a_bzrdir, name=name,
3167
append_revisions_only=append_revisions_only)
3168
medium = a_bzrdir._client._medium
3169
if medium._is_remote_before((1, 13)):
3170
return self._vfs_initialize(a_bzrdir, name=name,
3171
append_revisions_only=append_revisions_only)
3172
# Creating on a remote bzr dir.
3173
# 2) try direct creation via RPC
3174
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
3176
# XXX JRV20100304: Support creating colocated branches
3177
raise errors.NoColocatedBranchSupport(self)
3178
verb = 'BzrDir.create_branch'
3180
response = a_bzrdir._call(verb, path, network_name)
3181
except errors.UnknownSmartMethod:
3182
# Fallback - use vfs methods
3183
medium._remember_remote_is_before((1, 13))
3184
return self._vfs_initialize(a_bzrdir, name=name,
3185
append_revisions_only=append_revisions_only)
3186
if response[0] != 'ok':
3187
raise errors.UnexpectedSmartServerResponse(response)
3188
# Turn the response into a RemoteRepository object.
3189
format = RemoteBranchFormat(network_name=response[1])
3190
repo_format = response_tuple_to_repo_format(response[3:])
3191
repo_path = response[2]
3192
if repository is not None:
3193
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
3194
url_diff = urlutils.relative_url(repository.user_url,
3197
raise AssertionError(
3198
'repository.user_url %r does not match URL from server '
3199
'response (%r + %r)'
3200
% (repository.user_url, a_bzrdir.user_url, repo_path))
3201
remote_repo = repository
3204
repo_bzrdir = a_bzrdir
3206
repo_bzrdir = RemoteBzrDir(
3207
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
3209
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3210
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
3211
format=format, setup_stacking=False, name=name)
3212
if append_revisions_only:
3213
remote_branch.set_append_revisions_only(append_revisions_only)
3214
# XXX: We know this is a new branch, so it must have revno 0, revid
3215
# NULL_REVISION. Creating the branch locked would make this be unable
3216
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3217
remote_branch._last_revision_info_cache = 0, NULL_REVISION
3218
return remote_branch
3220
def make_tags(self, branch):
3222
return self._custom_format.make_tags(branch)
3224
def supports_tags(self):
3225
# Remote branches might support tags, but we won't know until we
3226
# access the real remote branch.
3228
return self._custom_format.supports_tags()
3230
def supports_stacking(self):
3232
return self._custom_format.supports_stacking()
3234
def supports_set_append_revisions_only(self):
3236
return self._custom_format.supports_set_append_revisions_only()
3238
def _use_default_local_heads_to_fetch(self):
3239
# If the branch format is a metadir format *and* its heads_to_fetch
3240
# implementation is not overridden vs the base class, we can use the
3241
# base class logic rather than use the heads_to_fetch RPC. This is
3242
# usually cheaper in terms of net round trips, as the last-revision and
3243
# tags info fetched is cached and would be fetched anyway.
3245
if isinstance(self._custom_format, branch.BranchFormatMetadir):
3246
branch_class = self._custom_format._branch_class()
3247
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
3248
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
3253
class RemoteBranchStore(_mod_config.IniFileStore):
3254
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3256
Note that this is specific to bzr-based formats.
3259
def __init__(self, branch):
3260
super(RemoteBranchStore, self).__init__()
3261
self.branch = branch
3263
self._real_store = None
3265
def external_url(self):
3266
return self.branch.user_url
3268
def _load_content(self):
3269
path = self.branch._remote_path()
3271
response, handler = self.branch._call_expecting_body(
3272
'Branch.get_config_file', path)
3273
except errors.UnknownSmartMethod:
3275
return self._real_store._load_content()
3276
if len(response) and response[0] != 'ok':
3277
raise errors.UnexpectedSmartServerResponse(response)
3278
return handler.read_body_bytes()
3280
def _save_content(self, content):
3281
path = self.branch._remote_path()
3283
response, handler = self.branch._call_with_body_bytes_expecting_body(
3284
'Branch.put_config_file', (path,
3285
self.branch._lock_token, self.branch._repo_lock_token),
3287
except errors.UnknownSmartMethod:
3289
return self._real_store._save_content(content)
3290
handler.cancel_read_body()
3291
if response != ('ok', ):
3292
raise errors.UnexpectedSmartServerResponse(response)
3294
def _ensure_real(self):
3295
self.branch._ensure_real()
3296
if self._real_store is None:
3297
self._real_store = _mod_config.BranchStore(self.branch)
3300
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
3301
"""Branch stored on a server accessed by HPSS RPC.
3303
At the moment most operations are mapped down to simple file operations.
3306
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3307
_client=None, format=None, setup_stacking=True, name=None,
3308
possible_transports=None):
3309
"""Create a RemoteBranch instance.
3311
:param real_branch: An optional local implementation of the branch
3312
format, usually accessing the data via the VFS.
3313
:param _client: Private parameter for testing.
3314
:param format: A RemoteBranchFormat object, None to create one
3315
automatically. If supplied it should have a network_name already
3317
:param setup_stacking: If True make an RPC call to determine the
3318
stacked (or not) status of the branch. If False assume the branch
3320
:param name: Colocated branch name
3322
# We intentionally don't call the parent class's __init__, because it
3323
# will try to assign to self.tags, which is a property in this subclass.
3324
# And the parent's __init__ doesn't do much anyway.
3325
self.bzrdir = remote_bzrdir
3327
if _client is not None:
3328
self._client = _client
3330
self._client = remote_bzrdir._client
3331
self.repository = remote_repository
3332
if real_branch is not None:
3333
self._real_branch = real_branch
3334
# Give the remote repository the matching real repo.
3335
real_repo = self._real_branch.repository
3336
if isinstance(real_repo, RemoteRepository):
3337
real_repo._ensure_real()
3338
real_repo = real_repo._real_repository
3339
self.repository._set_real_repository(real_repo)
3340
# Give the branch the remote repository to let fast-pathing happen.
3341
self._real_branch.repository = self.repository
3343
self._real_branch = None
3344
# Fill out expected attributes of branch for bzrlib API users.
3345
self._clear_cached_state()
3346
# TODO: deprecate self.base in favor of user_url
3347
self.base = self.bzrdir.user_url
3349
self._control_files = None
3350
self._lock_mode = None
3351
self._lock_token = None
3352
self._repo_lock_token = None
3353
self._lock_count = 0
3354
self._leave_lock = False
3355
self.conf_store = None
3356
# Setup a format: note that we cannot call _ensure_real until all the
3357
# attributes above are set: This code cannot be moved higher up in this
3360
self._format = RemoteBranchFormat()
3361
if real_branch is not None:
3362
self._format._network_name = \
3363
self._real_branch._format.network_name()
3365
self._format = format
3366
# when we do _ensure_real we may need to pass ignore_fallbacks to the
3367
# branch.open_branch method.
3368
self._real_ignore_fallbacks = not setup_stacking
3369
if not self._format._network_name:
3370
# Did not get from open_branchV2 - old server.
3372
self._format._network_name = \
3373
self._real_branch._format.network_name()
3374
self.tags = self._format.make_tags(self)
3375
# The base class init is not called, so we duplicate this:
3376
hooks = branch.Branch.hooks['open']
3379
self._is_stacked = False
3381
self._setup_stacking(possible_transports)
3383
def _setup_stacking(self, possible_transports):
3384
# configure stacking into the remote repository, by reading it from
3387
fallback_url = self.get_stacked_on_url()
3388
except (errors.NotStacked, errors.UnstackableBranchFormat,
3389
errors.UnstackableRepositoryFormat), e:
3391
self._is_stacked = True
3392
if possible_transports is None:
3393
possible_transports = []
3395
possible_transports = list(possible_transports)
3396
possible_transports.append(self.bzrdir.root_transport)
3397
self._activate_fallback_location(fallback_url,
3398
possible_transports=possible_transports)
3400
def _get_config(self):
3401
return RemoteBranchConfig(self)
3403
def _get_config_store(self):
3404
if self.conf_store is None:
3405
self.conf_store = RemoteBranchStore(self)
3406
return self.conf_store
3408
def _get_real_transport(self):
3409
# if we try vfs access, return the real branch's vfs transport
3411
return self._real_branch._transport
3413
_transport = property(_get_real_transport)
3416
return "%s(%s)" % (self.__class__.__name__, self.base)
3420
def _ensure_real(self):
3421
"""Ensure that there is a _real_branch set.
3423
Used before calls to self._real_branch.
3425
if self._real_branch is None:
3426
if not vfs.vfs_enabled():
3427
raise AssertionError('smart server vfs must be enabled '
3428
'to use vfs implementation')
3429
self.bzrdir._ensure_real()
3430
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
3431
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
3432
# The remote branch and the real branch shares the same store. If
3433
# we don't, there will always be cases where one of the stores
3434
# doesn't see an update made on the other.
3435
self._real_branch.conf_store = self.conf_store
3436
if self.repository._real_repository is None:
3437
# Give the remote repository the matching real repo.
3438
real_repo = self._real_branch.repository
3439
if isinstance(real_repo, RemoteRepository):
3440
real_repo._ensure_real()
3441
real_repo = real_repo._real_repository
3442
self.repository._set_real_repository(real_repo)
3443
# Give the real branch the remote repository to let fast-pathing
3445
self._real_branch.repository = self.repository
3446
if self._lock_mode == 'r':
3447
self._real_branch.lock_read()
3448
elif self._lock_mode == 'w':
3449
self._real_branch.lock_write(token=self._lock_token)
3451
def _translate_error(self, err, **context):
3452
self.repository._translate_error(err, branch=self, **context)
3454
def _clear_cached_state(self):
3455
super(RemoteBranch, self)._clear_cached_state()
3456
if self._real_branch is not None:
3457
self._real_branch._clear_cached_state()
3459
def _clear_cached_state_of_remote_branch_only(self):
3460
"""Like _clear_cached_state, but doesn't clear the cache of
3463
This is useful when falling back to calling a method of
3464
self._real_branch that changes state. In that case the underlying
3465
branch changes, so we need to invalidate this RemoteBranch's cache of
3466
it. However, there's no need to invalidate the _real_branch's cache
3467
too, in fact doing so might harm performance.
3469
super(RemoteBranch, self)._clear_cached_state()
3472
def control_files(self):
3473
# Defer actually creating RemoteBranchLockableFiles until its needed,
3474
# because it triggers an _ensure_real that we otherwise might not need.
3475
if self._control_files is None:
3476
self._control_files = RemoteBranchLockableFiles(
3477
self.bzrdir, self._client)
3478
return self._control_files
3480
def get_physical_lock_status(self):
3481
"""See Branch.get_physical_lock_status()."""
3483
response = self._client.call('Branch.get_physical_lock_status',
3484
self._remote_path())
3485
except errors.UnknownSmartMethod:
3487
return self._real_branch.get_physical_lock_status()
3488
if response[0] not in ('yes', 'no'):
3489
raise errors.UnexpectedSmartServerResponse(response)
3490
return (response[0] == 'yes')
3492
def get_stacked_on_url(self):
3493
"""Get the URL this branch is stacked against.
3495
:raises NotStacked: If the branch is not stacked.
3496
:raises UnstackableBranchFormat: If the branch does not support
3498
:raises UnstackableRepositoryFormat: If the repository does not support
3502
# there may not be a repository yet, so we can't use
3503
# self._translate_error, so we can't use self._call either.
3504
response = self._client.call('Branch.get_stacked_on_url',
3505
self._remote_path())
3506
except errors.ErrorFromSmartServer, err:
3507
# there may not be a repository yet, so we can't call through
3508
# its _translate_error
3509
_translate_error(err, branch=self)
3510
except errors.UnknownSmartMethod, err:
3512
return self._real_branch.get_stacked_on_url()
3513
if response[0] != 'ok':
3514
raise errors.UnexpectedSmartServerResponse(response)
3517
def set_stacked_on_url(self, url):
3518
branch.Branch.set_stacked_on_url(self, url)
3519
# We need the stacked_on_url to be visible both locally (to not query
3520
# it repeatedly) and remotely (so smart verbs can get it server side)
3521
# Without the following line,
3522
# bzrlib.tests.per_branch.test_create_clone.TestCreateClone
3523
# .test_create_clone_on_transport_stacked_hooks_get_stacked_branch
3524
# fails for remote branches -- vila 2012-01-04
3525
self.conf_store.save_changes()
3527
self._is_stacked = False
3529
self._is_stacked = True
3531
def _vfs_get_tags_bytes(self):
3533
return self._real_branch._get_tags_bytes()
3536
def _get_tags_bytes(self):
3537
if self._tags_bytes is None:
3538
self._tags_bytes = self._get_tags_bytes_via_hpss()
3539
return self._tags_bytes
3541
def _get_tags_bytes_via_hpss(self):
3542
medium = self._client._medium
3543
if medium._is_remote_before((1, 13)):
3544
return self._vfs_get_tags_bytes()
3546
response = self._call('Branch.get_tags_bytes', self._remote_path())
3547
except errors.UnknownSmartMethod:
3548
medium._remember_remote_is_before((1, 13))
3549
return self._vfs_get_tags_bytes()
3552
def _vfs_set_tags_bytes(self, bytes):
3554
return self._real_branch._set_tags_bytes(bytes)
3556
def _set_tags_bytes(self, bytes):
3557
if self.is_locked():
3558
self._tags_bytes = bytes
3559
medium = self._client._medium
3560
if medium._is_remote_before((1, 18)):
3561
self._vfs_set_tags_bytes(bytes)
3565
self._remote_path(), self._lock_token, self._repo_lock_token)
3566
response = self._call_with_body_bytes(
3567
'Branch.set_tags_bytes', args, bytes)
3568
except errors.UnknownSmartMethod:
3569
medium._remember_remote_is_before((1, 18))
3570
self._vfs_set_tags_bytes(bytes)
3572
def lock_read(self):
3573
"""Lock the branch for read operations.
3575
:return: A bzrlib.lock.LogicalLockResult.
3577
self.repository.lock_read()
3578
if not self._lock_mode:
3579
self._note_lock('r')
3580
self._lock_mode = 'r'
3581
self._lock_count = 1
3582
if self._real_branch is not None:
3583
self._real_branch.lock_read()
3585
self._lock_count += 1
3586
return lock.LogicalLockResult(self.unlock)
3588
def _remote_lock_write(self, token):
3590
branch_token = repo_token = ''
3592
branch_token = token
3593
repo_token = self.repository.lock_write().repository_token
3594
self.repository.unlock()
3595
err_context = {'token': token}
3597
response = self._call(
3598
'Branch.lock_write', self._remote_path(), branch_token,
3599
repo_token or '', **err_context)
3600
except errors.LockContention, e:
3601
# The LockContention from the server doesn't have any
3602
# information about the lock_url. We re-raise LockContention
3603
# with valid lock_url.
3604
raise errors.LockContention('(remote lock)',
3605
self.repository.base.split('.bzr/')[0])
3606
if response[0] != 'ok':
3607
raise errors.UnexpectedSmartServerResponse(response)
3608
ok, branch_token, repo_token = response
3609
return branch_token, repo_token
3611
def lock_write(self, token=None):
3612
if not self._lock_mode:
3613
self._note_lock('w')
3614
# Lock the branch and repo in one remote call.
3615
remote_tokens = self._remote_lock_write(token)
3616
self._lock_token, self._repo_lock_token = remote_tokens
3617
if not self._lock_token:
3618
raise SmartProtocolError('Remote server did not return a token!')
3619
# Tell the self.repository object that it is locked.
3620
self.repository.lock_write(
3621
self._repo_lock_token, _skip_rpc=True)
3623
if self._real_branch is not None:
3624
self._real_branch.lock_write(token=self._lock_token)
3625
if token is not None:
3626
self._leave_lock = True
3628
self._leave_lock = False
3629
self._lock_mode = 'w'
3630
self._lock_count = 1
3631
elif self._lock_mode == 'r':
3632
raise errors.ReadOnlyError(self)
3634
if token is not None:
3635
# A token was given to lock_write, and we're relocking, so
3636
# check that the given token actually matches the one we
3638
if token != self._lock_token:
3639
raise errors.TokenMismatch(token, self._lock_token)
3640
self._lock_count += 1
3641
# Re-lock the repository too.
3642
self.repository.lock_write(self._repo_lock_token)
3643
return BranchWriteLockResult(self.unlock, self._lock_token or None)
3645
def _unlock(self, branch_token, repo_token):
3646
err_context = {'token': str((branch_token, repo_token))}
3647
response = self._call(
3648
'Branch.unlock', self._remote_path(), branch_token,
3649
repo_token or '', **err_context)
3650
if response == ('ok',):
3652
raise errors.UnexpectedSmartServerResponse(response)
3654
@only_raises(errors.LockNotHeld, errors.LockBroken)
3657
self._lock_count -= 1
3658
if not self._lock_count:
3659
if self.conf_store is not None:
3660
self.conf_store.save_changes()
3661
self._clear_cached_state()
3662
mode = self._lock_mode
3663
self._lock_mode = None
3664
if self._real_branch is not None:
3665
if (not self._leave_lock and mode == 'w' and
3666
self._repo_lock_token):
3667
# If this RemoteBranch will remove the physical lock
3668
# for the repository, make sure the _real_branch
3669
# doesn't do it first. (Because the _real_branch's
3670
# repository is set to be the RemoteRepository.)
3671
self._real_branch.repository.leave_lock_in_place()
3672
self._real_branch.unlock()
3674
# Only write-locked branched need to make a remote method
3675
# call to perform the unlock.
3677
if not self._lock_token:
3678
raise AssertionError('Locked, but no token!')
3679
branch_token = self._lock_token
3680
repo_token = self._repo_lock_token
3681
self._lock_token = None
3682
self._repo_lock_token = None
3683
if not self._leave_lock:
3684
self._unlock(branch_token, repo_token)
3686
self.repository.unlock()
3688
def break_lock(self):
3690
response = self._call(
3691
'Branch.break_lock', self._remote_path())
3692
except errors.UnknownSmartMethod:
3694
return self._real_branch.break_lock()
3695
if response != ('ok',):
3696
raise errors.UnexpectedSmartServerResponse(response)
3698
def leave_lock_in_place(self):
3699
if not self._lock_token:
3700
raise NotImplementedError(self.leave_lock_in_place)
3701
self._leave_lock = True
3703
def dont_leave_lock_in_place(self):
3704
if not self._lock_token:
3705
raise NotImplementedError(self.dont_leave_lock_in_place)
3706
self._leave_lock = False
3709
def get_rev_id(self, revno, history=None):
3711
return _mod_revision.NULL_REVISION
3712
last_revision_info = self.last_revision_info()
3713
ok, result = self.repository.get_rev_id_for_revno(
3714
revno, last_revision_info)
3717
missing_parent = result[1]
3718
# Either the revision named by the server is missing, or its parent
3719
# is. Call get_parent_map to determine which, so that we report a
3721
parent_map = self.repository.get_parent_map([missing_parent])
3722
if missing_parent in parent_map:
3723
missing_parent = parent_map[missing_parent]
3724
raise errors.RevisionNotPresent(missing_parent, self.repository)
3726
def _read_last_revision_info(self):
3727
response = self._call('Branch.last_revision_info', self._remote_path())
3728
if response[0] != 'ok':
3729
raise SmartProtocolError('unexpected response code %s' % (response,))
3730
revno = int(response[1])
3731
last_revision = response[2]
3732
return (revno, last_revision)
3734
def _gen_revision_history(self):
3735
"""See Branch._gen_revision_history()."""
3736
if self._is_stacked:
3738
return self._real_branch._gen_revision_history()
3739
response_tuple, response_handler = self._call_expecting_body(
3740
'Branch.revision_history', self._remote_path())
3741
if response_tuple[0] != 'ok':
3742
raise errors.UnexpectedSmartServerResponse(response_tuple)
3743
result = response_handler.read_body_bytes().split('\x00')
3748
def _remote_path(self):
3749
return self.bzrdir._path_for_remote_call(self._client)
3751
def _set_last_revision_descendant(self, revision_id, other_branch,
3752
allow_diverged=False, allow_overwrite_descendant=False):
3753
# This performs additional work to meet the hook contract; while its
3754
# undesirable, we have to synthesise the revno to call the hook, and
3755
# not calling the hook is worse as it means changes can't be prevented.
3756
# Having calculated this though, we can't just call into
3757
# set_last_revision_info as a simple call, because there is a set_rh
3758
# hook that some folk may still be using.
3759
old_revno, old_revid = self.last_revision_info()
3760
history = self._lefthand_history(revision_id)
3761
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3762
err_context = {'other_branch': other_branch}
3763
response = self._call('Branch.set_last_revision_ex',
3764
self._remote_path(), self._lock_token, self._repo_lock_token,
3765
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
3767
self._clear_cached_state()
3768
if len(response) != 3 and response[0] != 'ok':
3769
raise errors.UnexpectedSmartServerResponse(response)
3770
new_revno, new_revision_id = response[1:]
3771
self._last_revision_info_cache = new_revno, new_revision_id
3772
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3773
if self._real_branch is not None:
3774
cache = new_revno, new_revision_id
3775
self._real_branch._last_revision_info_cache = cache
3777
def _set_last_revision(self, revision_id):
3778
old_revno, old_revid = self.last_revision_info()
3779
# This performs additional work to meet the hook contract; while its
3780
# undesirable, we have to synthesise the revno to call the hook, and
3781
# not calling the hook is worse as it means changes can't be prevented.
3782
# Having calculated this though, we can't just call into
3783
# set_last_revision_info as a simple call, because there is a set_rh
3784
# hook that some folk may still be using.
3785
history = self._lefthand_history(revision_id)
3786
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3787
self._clear_cached_state()
3788
response = self._call('Branch.set_last_revision',
3789
self._remote_path(), self._lock_token, self._repo_lock_token,
3791
if response != ('ok',):
3792
raise errors.UnexpectedSmartServerResponse(response)
3793
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3795
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3797
def set_revision_history(self, rev_history):
3798
"""See Branch.set_revision_history."""
3799
self._set_revision_history(rev_history)
3802
def _set_revision_history(self, rev_history):
3803
# Send just the tip revision of the history; the server will generate
3804
# the full history from that. If the revision doesn't exist in this
3805
# branch, NoSuchRevision will be raised.
3806
if rev_history == []:
3809
rev_id = rev_history[-1]
3810
self._set_last_revision(rev_id)
3811
for hook in branch.Branch.hooks['set_rh']:
3812
hook(self, rev_history)
3813
self._cache_revision_history(rev_history)
3815
def _get_parent_location(self):
3816
medium = self._client._medium
3817
if medium._is_remote_before((1, 13)):
3818
return self._vfs_get_parent_location()
3820
response = self._call('Branch.get_parent', self._remote_path())
3821
except errors.UnknownSmartMethod:
3822
medium._remember_remote_is_before((1, 13))
3823
return self._vfs_get_parent_location()
3824
if len(response) != 1:
3825
raise errors.UnexpectedSmartServerResponse(response)
3826
parent_location = response[0]
3827
if parent_location == '':
3829
return parent_location
3831
def _vfs_get_parent_location(self):
3833
return self._real_branch._get_parent_location()
3835
def _set_parent_location(self, url):
3836
medium = self._client._medium
3837
if medium._is_remote_before((1, 15)):
3838
return self._vfs_set_parent_location(url)
3840
call_url = url or ''
3841
if type(call_url) is not str:
3842
raise AssertionError('url must be a str or None (%s)' % url)
3843
response = self._call('Branch.set_parent_location',
3844
self._remote_path(), self._lock_token, self._repo_lock_token,
3846
except errors.UnknownSmartMethod:
3847
medium._remember_remote_is_before((1, 15))
3848
return self._vfs_set_parent_location(url)
3850
raise errors.UnexpectedSmartServerResponse(response)
3852
def _vfs_set_parent_location(self, url):
3854
return self._real_branch._set_parent_location(url)
3857
def pull(self, source, overwrite=False, stop_revision=None,
3859
self._clear_cached_state_of_remote_branch_only()
3861
return self._real_branch.pull(
3862
source, overwrite=overwrite, stop_revision=stop_revision,
3863
_override_hook_target=self, **kwargs)
3866
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
3868
return self._real_branch.push(
3869
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
3870
_override_hook_source_branch=self)
3872
def is_locked(self):
3873
return self._lock_count >= 1
3876
def revision_id_to_dotted_revno(self, revision_id):
3877
"""Given a revision id, return its dotted revno.
3879
:return: a tuple like (1,) or (400,1,3).
3882
response = self._call('Branch.revision_id_to_revno',
3883
self._remote_path(), revision_id)
3884
except errors.UnknownSmartMethod:
3886
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3887
if response[0] == 'ok':
3888
return tuple([int(x) for x in response[1:]])
3890
raise errors.UnexpectedSmartServerResponse(response)
3893
def revision_id_to_revno(self, revision_id):
3894
"""Given a revision id on the branch mainline, return its revno.
3899
response = self._call('Branch.revision_id_to_revno',
3900
self._remote_path(), revision_id)
3901
except errors.UnknownSmartMethod:
3903
return self._real_branch.revision_id_to_revno(revision_id)
3904
if response[0] == 'ok':
3905
if len(response) == 2:
3906
return int(response[1])
3907
raise NoSuchRevision(self, revision_id)
3909
raise errors.UnexpectedSmartServerResponse(response)
3912
def set_last_revision_info(self, revno, revision_id):
3913
# XXX: These should be returned by the set_last_revision_info verb
3914
old_revno, old_revid = self.last_revision_info()
3915
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3916
if not revision_id or not isinstance(revision_id, basestring):
3917
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3919
response = self._call('Branch.set_last_revision_info',
3920
self._remote_path(), self._lock_token, self._repo_lock_token,
3921
str(revno), revision_id)
3922
except errors.UnknownSmartMethod:
3924
self._clear_cached_state_of_remote_branch_only()
3925
self._real_branch.set_last_revision_info(revno, revision_id)
3926
self._last_revision_info_cache = revno, revision_id
3928
if response == ('ok',):
3929
self._clear_cached_state()
3930
self._last_revision_info_cache = revno, revision_id
3931
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3932
# Update the _real_branch's cache too.
3933
if self._real_branch is not None:
3934
cache = self._last_revision_info_cache
3935
self._real_branch._last_revision_info_cache = cache
3937
raise errors.UnexpectedSmartServerResponse(response)
3940
def generate_revision_history(self, revision_id, last_rev=None,
3942
medium = self._client._medium
3943
if not medium._is_remote_before((1, 6)):
3944
# Use a smart method for 1.6 and above servers
3946
self._set_last_revision_descendant(revision_id, other_branch,
3947
allow_diverged=True, allow_overwrite_descendant=True)
3949
except errors.UnknownSmartMethod:
3950
medium._remember_remote_is_before((1, 6))
3951
self._clear_cached_state_of_remote_branch_only()
3952
self._set_revision_history(self._lefthand_history(revision_id,
3953
last_rev=last_rev,other_branch=other_branch))
3955
def set_push_location(self, location):
3956
self._set_config_location('push_location', location)
3958
def heads_to_fetch(self):
3959
if self._format._use_default_local_heads_to_fetch():
3960
# We recognise this format, and its heads-to-fetch implementation
3961
# is the default one (tip + tags). In this case it's cheaper to
3962
# just use the default implementation rather than a special RPC as
3963
# the tip and tags data is cached.
3964
return branch.Branch.heads_to_fetch(self)
3965
medium = self._client._medium
3966
if medium._is_remote_before((2, 4)):
3967
return self._vfs_heads_to_fetch()
3969
return self._rpc_heads_to_fetch()
3970
except errors.UnknownSmartMethod:
3971
medium._remember_remote_is_before((2, 4))
3972
return self._vfs_heads_to_fetch()
3974
def _rpc_heads_to_fetch(self):
3975
response = self._call('Branch.heads_to_fetch', self._remote_path())
3976
if len(response) != 2:
3977
raise errors.UnexpectedSmartServerResponse(response)
3978
must_fetch, if_present_fetch = response
3979
return set(must_fetch), set(if_present_fetch)
3981
def _vfs_heads_to_fetch(self):
3983
return self._real_branch.heads_to_fetch()
3986
class RemoteConfig(object):
3987
"""A Config that reads and writes from smart verbs.
3989
It is a low-level object that considers config data to be name/value pairs
3990
that may be associated with a section. Assigning meaning to the these
3991
values is done at higher levels like bzrlib.config.TreeConfig.
3994
def get_option(self, name, section=None, default=None):
3995
"""Return the value associated with a named option.
3997
:param name: The name of the value
3998
:param section: The section the option is in (if any)
3999
:param default: The value to return if the value is not set
4000
:return: The value or default value
4003
configobj = self._get_configobj()
4006
section_obj = configobj
4009
section_obj = configobj[section]
4012
if section_obj is None:
4015
value = section_obj.get(name, default)
4016
except errors.UnknownSmartMethod:
4017
value = self._vfs_get_option(name, section, default)
4018
for hook in _mod_config.OldConfigHooks['get']:
4019
hook(self, name, value)
4022
def _response_to_configobj(self, response):
4023
if len(response[0]) and response[0][0] != 'ok':
4024
raise errors.UnexpectedSmartServerResponse(response)
4025
lines = response[1].read_body_bytes().splitlines()
4026
conf = _mod_config.ConfigObj(lines, encoding='utf-8')
4027
for hook in _mod_config.OldConfigHooks['load']:
4032
class RemoteBranchConfig(RemoteConfig):
4033
"""A RemoteConfig for Branches."""
4035
def __init__(self, branch):
4036
self._branch = branch
4038
def _get_configobj(self):
4039
path = self._branch._remote_path()
4040
response = self._branch._client.call_expecting_body(
4041
'Branch.get_config_file', path)
4042
return self._response_to_configobj(response)
4044
def set_option(self, value, name, section=None):
4045
"""Set the value associated with a named option.
4047
:param value: The value to set
4048
:param name: The name of the value to set
4049
:param section: The section the option is in (if any)
4051
medium = self._branch._client._medium
4052
if medium._is_remote_before((1, 14)):
4053
return self._vfs_set_option(value, name, section)
4054
if isinstance(value, dict):
4055
if medium._is_remote_before((2, 2)):
4056
return self._vfs_set_option(value, name, section)
4057
return self._set_config_option_dict(value, name, section)
4059
return self._set_config_option(value, name, section)
4061
def _set_config_option(self, value, name, section):
4063
path = self._branch._remote_path()
4064
response = self._branch._client.call('Branch.set_config_option',
4065
path, self._branch._lock_token, self._branch._repo_lock_token,
4066
value.encode('utf8'), name, section or '')
4067
except errors.UnknownSmartMethod:
4068
medium = self._branch._client._medium
4069
medium._remember_remote_is_before((1, 14))
4070
return self._vfs_set_option(value, name, section)
4072
raise errors.UnexpectedSmartServerResponse(response)
4074
def _serialize_option_dict(self, option_dict):
4076
for key, value in option_dict.items():
4077
if isinstance(key, unicode):
4078
key = key.encode('utf8')
4079
if isinstance(value, unicode):
4080
value = value.encode('utf8')
4081
utf8_dict[key] = value
4082
return bencode.bencode(utf8_dict)
4084
def _set_config_option_dict(self, value, name, section):
4086
path = self._branch._remote_path()
4087
serialised_dict = self._serialize_option_dict(value)
4088
response = self._branch._client.call(
4089
'Branch.set_config_option_dict',
4090
path, self._branch._lock_token, self._branch._repo_lock_token,
4091
serialised_dict, name, section or '')
4092
except errors.UnknownSmartMethod:
4093
medium = self._branch._client._medium
4094
medium._remember_remote_is_before((2, 2))
4095
return self._vfs_set_option(value, name, section)
4097
raise errors.UnexpectedSmartServerResponse(response)
4099
def _real_object(self):
4100
self._branch._ensure_real()
4101
return self._branch._real_branch
4103
def _vfs_set_option(self, value, name, section=None):
4104
return self._real_object()._get_config().set_option(
4105
value, name, section)
4108
class RemoteBzrDirConfig(RemoteConfig):
4109
"""A RemoteConfig for BzrDirs."""
4111
def __init__(self, bzrdir):
4112
self._bzrdir = bzrdir
4114
def _get_configobj(self):
4115
medium = self._bzrdir._client._medium
4116
verb = 'BzrDir.get_config_file'
4117
if medium._is_remote_before((1, 15)):
4118
raise errors.UnknownSmartMethod(verb)
4119
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4120
response = self._bzrdir._call_expecting_body(
4122
return self._response_to_configobj(response)
4124
def _vfs_get_option(self, name, section, default):
4125
return self._real_object()._get_config().get_option(
4126
name, section, default)
4128
def set_option(self, value, name, section=None):
4129
"""Set the value associated with a named option.
4131
:param value: The value to set
4132
:param name: The name of the value to set
4133
:param section: The section the option is in (if any)
4135
return self._real_object()._get_config().set_option(
4136
value, name, section)
4138
def _real_object(self):
4139
self._bzrdir._ensure_real()
4140
return self._bzrdir._real_bzrdir
4143
def _extract_tar(tar, to_dir):
4144
"""Extract all the contents of a tarfile object.
4146
A replacement for extractall, which is not present in python2.4
4149
tar.extract(tarinfo, to_dir)
4152
error_translators = registry.Registry()
4153
no_context_error_translators = registry.Registry()
4156
def _translate_error(err, **context):
4157
"""Translate an ErrorFromSmartServer into a more useful error.
4159
Possible context keys:
4167
If the error from the server doesn't match a known pattern, then
4168
UnknownErrorFromSmartServer is raised.
4172
return context[name]
4173
except KeyError, key_err:
4174
mutter('Missing key %r in context %r', key_err.args[0], context)
4177
"""Get the path from the context if present, otherwise use first error
4181
return context['path']
4182
except KeyError, key_err:
4184
return err.error_args[0]
4185
except IndexError, idx_err:
4187
'Missing key %r in context %r', key_err.args[0], context)
4191
translator = error_translators.get(err.error_verb)
4195
raise translator(err, find, get_path)
4197
translator = no_context_error_translators.get(err.error_verb)
4199
raise errors.UnknownErrorFromSmartServer(err)
4201
raise translator(err)
4204
error_translators.register('NoSuchRevision',
4205
lambda err, find, get_path: NoSuchRevision(
4206
find('branch'), err.error_args[0]))
4207
error_translators.register('nosuchrevision',
4208
lambda err, find, get_path: NoSuchRevision(
4209
find('repository'), err.error_args[0]))
4211
def _translate_nobranch_error(err, find, get_path):
4212
if len(err.error_args) >= 1:
4213
extra = err.error_args[0]
4216
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4219
error_translators.register('nobranch', _translate_nobranch_error)
4220
error_translators.register('norepository',
4221
lambda err, find, get_path: errors.NoRepositoryPresent(
4223
error_translators.register('UnlockableTransport',
4224
lambda err, find, get_path: errors.UnlockableTransport(
4225
find('bzrdir').root_transport))
4226
error_translators.register('TokenMismatch',
4227
lambda err, find, get_path: errors.TokenMismatch(
4228
find('token'), '(remote token)'))
4229
error_translators.register('Diverged',
4230
lambda err, find, get_path: errors.DivergedBranches(
4231
find('branch'), find('other_branch')))
4232
error_translators.register('NotStacked',
4233
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4235
def _translate_PermissionDenied(err, find, get_path):
4237
if len(err.error_args) >= 2:
4238
extra = err.error_args[1]
4241
return errors.PermissionDenied(path, extra=extra)
4243
error_translators.register('PermissionDenied', _translate_PermissionDenied)
4244
error_translators.register('ReadError',
4245
lambda err, find, get_path: errors.ReadError(get_path()))
4246
error_translators.register('NoSuchFile',
4247
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4248
error_translators.register('TokenLockingNotSupported',
4249
lambda err, find, get_path: errors.TokenLockingNotSupported(
4250
find('repository')))
4251
error_translators.register('UnsuspendableWriteGroup',
4252
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4253
repository=find('repository')))
4254
error_translators.register('UnresumableWriteGroup',
4255
lambda err, find, get_path: errors.UnresumableWriteGroup(
4256
repository=find('repository'), write_groups=err.error_args[0],
4257
reason=err.error_args[1]))
4258
no_context_error_translators.register('IncompatibleRepositories',
4259
lambda err: errors.IncompatibleRepositories(
4260
err.error_args[0], err.error_args[1], err.error_args[2]))
4261
no_context_error_translators.register('LockContention',
4262
lambda err: errors.LockContention('(remote lock)'))
4263
no_context_error_translators.register('LockFailed',
4264
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
4265
no_context_error_translators.register('TipChangeRejected',
4266
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4267
no_context_error_translators.register('UnstackableBranchFormat',
4268
lambda err: errors.UnstackableBranchFormat(*err.error_args))
4269
no_context_error_translators.register('UnstackableRepositoryFormat',
4270
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4271
no_context_error_translators.register('FileExists',
4272
lambda err: errors.FileExists(err.error_args[0]))
4273
no_context_error_translators.register('DirectoryNotEmpty',
4274
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
4276
def _translate_short_readv_error(err):
4277
args = err.error_args
4278
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
4281
no_context_error_translators.register('ShortReadvError',
4282
_translate_short_readv_error)
4284
def _translate_unicode_error(err):
4285
encoding = str(err.error_args[0]) # encoding must always be a string
4286
val = err.error_args[1]
4287
start = int(err.error_args[2])
4288
end = int(err.error_args[3])
4289
reason = str(err.error_args[4]) # reason must always be a string
4290
if val.startswith('u:'):
4291
val = val[2:].decode('utf-8')
4292
elif val.startswith('s:'):
4293
val = val[2:].decode('base64')
4294
if err.error_verb == 'UnicodeDecodeError':
4295
raise UnicodeDecodeError(encoding, val, start, end, reason)
4296
elif err.error_verb == 'UnicodeEncodeError':
4297
raise UnicodeEncodeError(encoding, val, start, end, reason)
4299
no_context_error_translators.register('UnicodeEncodeError',
4300
_translate_unicode_error)
4301
no_context_error_translators.register('UnicodeDecodeError',
4302
_translate_unicode_error)
4303
no_context_error_translators.register('ReadOnlyError',
4304
lambda err: errors.TransportNotPossible('readonly transport'))
4305
no_context_error_translators.register('MemoryError',
4306
lambda err: errors.BzrError("remote server out of memory\n"
4307
"Retry non-remotely, or contact the server admin for details."))
4308
no_context_error_translators.register('RevisionNotPresent',
4309
lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
4311
no_context_error_translators.register('BzrCheckError',
4312
lambda err: errors.BzrCheckError(msg=err.error_args[0]))