1
# Copyright (C) 2006-2011 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22
bzrdir as _mod_bzrdir,
30
repository as _mod_repository,
31
revision as _mod_revision,
36
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
37
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
38
from bzrlib.errors import (
42
from bzrlib.lockable_files import LockableFiles
43
from bzrlib.smart import client, vfs, repository as smart_repo
44
from bzrlib.smart.client import _SmartClient
45
from bzrlib.revision import NULL_REVISION
46
from bzrlib.repository import RepositoryWriteLockResult
47
from bzrlib.trace import mutter, note, warning
50
class _RpcHelper(object):
51
"""Mixin class that helps with issuing RPCs."""
53
def _call(self, method, *args, **err_context):
55
return self._client.call(method, *args)
56
except errors.ErrorFromSmartServer, err:
57
self._translate_error(err, **err_context)
59
def _call_expecting_body(self, method, *args, **err_context):
61
return self._client.call_expecting_body(method, *args)
62
except errors.ErrorFromSmartServer, err:
63
self._translate_error(err, **err_context)
65
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
67
return self._client.call_with_body_bytes(method, args, body_bytes)
68
except errors.ErrorFromSmartServer, err:
69
self._translate_error(err, **err_context)
71
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
74
return self._client.call_with_body_bytes_expecting_body(
75
method, args, body_bytes)
76
except errors.ErrorFromSmartServer, err:
77
self._translate_error(err, **err_context)
80
def response_tuple_to_repo_format(response):
81
"""Convert a response tuple describing a repository format to a format."""
82
format = RemoteRepositoryFormat()
83
format._rich_root_data = (response[0] == 'yes')
84
format._supports_tree_reference = (response[1] == 'yes')
85
format._supports_external_lookups = (response[2] == 'yes')
86
format._network_name = response[3]
90
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
91
# does not have to be imported unless a remote format is involved.
93
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
94
"""Format representing bzrdirs accessed via a smart server"""
96
supports_workingtrees = False
99
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
100
# XXX: It's a bit ugly that the network name is here, because we'd
101
# like to believe that format objects are stateless or at least
102
# immutable, However, we do at least avoid mutating the name after
103
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
104
self._network_name = None
107
return "%s(_network_name=%r)" % (self.__class__.__name__,
110
def get_format_description(self):
111
if self._network_name:
112
real_format = controldir.network_format_registry.get(self._network_name)
113
return 'Remote: ' + real_format.get_format_description()
114
return 'bzr remote bzrdir'
116
def get_format_string(self):
117
raise NotImplementedError(self.get_format_string)
119
def network_name(self):
120
if self._network_name:
121
return self._network_name
123
raise AssertionError("No network name set.")
125
def initialize_on_transport(self, transport):
127
# hand off the request to the smart server
128
client_medium = transport.get_smart_medium()
129
except errors.NoSmartMedium:
130
# TODO: lookup the local format from a server hint.
131
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
132
return local_dir_format.initialize_on_transport(transport)
133
client = _SmartClient(client_medium)
134
path = client.remote_path_from_transport(transport)
136
response = client.call('BzrDirFormat.initialize', path)
137
except errors.ErrorFromSmartServer, err:
138
_translate_error(err, path=path)
139
if response[0] != 'ok':
140
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
141
format = RemoteBzrDirFormat()
142
self._supply_sub_formats_to(format)
143
return RemoteBzrDir(transport, format)
145
def parse_NoneTrueFalse(self, arg):
152
raise AssertionError("invalid arg %r" % arg)
154
def _serialize_NoneTrueFalse(self, arg):
161
def _serialize_NoneString(self, arg):
164
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
165
create_prefix=False, force_new_repo=False, stacked_on=None,
166
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
169
# hand off the request to the smart server
170
client_medium = transport.get_smart_medium()
171
except errors.NoSmartMedium:
174
# Decline to open it if the server doesn't support our required
175
# version (3) so that the VFS-based transport will do it.
176
if client_medium.should_probe():
178
server_version = client_medium.protocol_version()
179
if server_version != '2':
183
except errors.SmartProtocolError:
184
# Apparently there's no usable smart server there, even though
185
# the medium supports the smart protocol.
190
client = _SmartClient(client_medium)
191
path = client.remote_path_from_transport(transport)
192
if client_medium._is_remote_before((1, 16)):
195
# TODO: lookup the local format from a server hint.
196
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
197
self._supply_sub_formats_to(local_dir_format)
198
return local_dir_format.initialize_on_transport_ex(transport,
199
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
200
force_new_repo=force_new_repo, stacked_on=stacked_on,
201
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
202
make_working_trees=make_working_trees, shared_repo=shared_repo,
204
return self._initialize_on_transport_ex_rpc(client, path, transport,
205
use_existing_dir, create_prefix, force_new_repo, stacked_on,
206
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
208
def _initialize_on_transport_ex_rpc(self, client, path, transport,
209
use_existing_dir, create_prefix, force_new_repo, stacked_on,
210
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
212
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
213
args.append(self._serialize_NoneTrueFalse(create_prefix))
214
args.append(self._serialize_NoneTrueFalse(force_new_repo))
215
args.append(self._serialize_NoneString(stacked_on))
216
# stack_on_pwd is often/usually our transport
219
stack_on_pwd = transport.relpath(stack_on_pwd)
222
except errors.PathNotChild:
224
args.append(self._serialize_NoneString(stack_on_pwd))
225
args.append(self._serialize_NoneString(repo_format_name))
226
args.append(self._serialize_NoneTrueFalse(make_working_trees))
227
args.append(self._serialize_NoneTrueFalse(shared_repo))
228
request_network_name = self._network_name or \
229
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
231
response = client.call('BzrDirFormat.initialize_ex_1.16',
232
request_network_name, path, *args)
233
except errors.UnknownSmartMethod:
234
client._medium._remember_remote_is_before((1,16))
235
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
236
self._supply_sub_formats_to(local_dir_format)
237
return local_dir_format.initialize_on_transport_ex(transport,
238
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
239
force_new_repo=force_new_repo, stacked_on=stacked_on,
240
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
241
make_working_trees=make_working_trees, shared_repo=shared_repo,
243
except errors.ErrorFromSmartServer, err:
244
_translate_error(err, path=path)
245
repo_path = response[0]
246
bzrdir_name = response[6]
247
require_stacking = response[7]
248
require_stacking = self.parse_NoneTrueFalse(require_stacking)
249
format = RemoteBzrDirFormat()
250
format._network_name = bzrdir_name
251
self._supply_sub_formats_to(format)
252
bzrdir = RemoteBzrDir(transport, format, _client=client)
254
repo_format = response_tuple_to_repo_format(response[1:])
258
repo_bzrdir_format = RemoteBzrDirFormat()
259
repo_bzrdir_format._network_name = response[5]
260
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
264
final_stack = response[8] or None
265
final_stack_pwd = response[9] or None
267
final_stack_pwd = urlutils.join(
268
transport.base, final_stack_pwd)
269
remote_repo = RemoteRepository(repo_bzr, repo_format)
270
if len(response) > 10:
271
# Updated server verb that locks remotely.
272
repo_lock_token = response[10] or None
273
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
275
remote_repo.dont_leave_lock_in_place()
277
remote_repo.lock_write()
278
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
279
final_stack_pwd, require_stacking)
280
policy.acquire_repository()
284
bzrdir._format.set_branch_format(self.get_branch_format())
286
# The repo has already been created, but we need to make sure that
287
# we'll make a stackable branch.
288
bzrdir._format.require_stacking(_skip_repo=True)
289
return remote_repo, bzrdir, require_stacking, policy
291
def _open(self, transport):
292
return RemoteBzrDir(transport, self)
294
def __eq__(self, other):
295
if not isinstance(other, RemoteBzrDirFormat):
297
return self.get_format_description() == other.get_format_description()
299
def __return_repository_format(self):
300
# Always return a RemoteRepositoryFormat object, but if a specific bzr
301
# repository format has been asked for, tell the RemoteRepositoryFormat
302
# that it should use that for init() etc.
303
result = RemoteRepositoryFormat()
304
custom_format = getattr(self, '_repository_format', None)
306
if isinstance(custom_format, RemoteRepositoryFormat):
309
# We will use the custom format to create repositories over the
310
# wire; expose its details like rich_root_data for code to
312
result._custom_format = custom_format
315
def get_branch_format(self):
316
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
317
if not isinstance(result, RemoteBranchFormat):
318
new_result = RemoteBranchFormat()
319
new_result._custom_format = result
321
self.set_branch_format(new_result)
325
repository_format = property(__return_repository_format,
326
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
329
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
330
"""Control directory on a remote server, accessed via bzr:// or similar."""
332
def __init__(self, transport, format, _client=None, _force_probe=False):
333
"""Construct a RemoteBzrDir.
335
:param _client: Private parameter for testing. Disables probing and the
336
use of a real bzrdir.
338
_mod_bzrdir.BzrDir.__init__(self, transport, format)
339
# this object holds a delegated bzrdir that uses file-level operations
340
# to talk to the other side
341
self._real_bzrdir = None
342
self._has_working_tree = None
343
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
344
# create_branch for details.
345
self._next_open_branch_result = None
348
medium = transport.get_smart_medium()
349
self._client = client._SmartClient(medium)
351
self._client = _client
358
return '%s(%r)' % (self.__class__.__name__, self._client)
360
def _probe_bzrdir(self):
361
medium = self._client._medium
362
path = self._path_for_remote_call(self._client)
363
if medium._is_remote_before((2, 1)):
367
self._rpc_open_2_1(path)
369
except errors.UnknownSmartMethod:
370
medium._remember_remote_is_before((2, 1))
373
def _rpc_open_2_1(self, path):
374
response = self._call('BzrDir.open_2.1', path)
375
if response == ('no',):
376
raise errors.NotBranchError(path=self.root_transport.base)
377
elif response[0] == 'yes':
378
if response[1] == 'yes':
379
self._has_working_tree = True
380
elif response[1] == 'no':
381
self._has_working_tree = False
383
raise errors.UnexpectedSmartServerResponse(response)
385
raise errors.UnexpectedSmartServerResponse(response)
387
def _rpc_open(self, path):
388
response = self._call('BzrDir.open', path)
389
if response not in [('yes',), ('no',)]:
390
raise errors.UnexpectedSmartServerResponse(response)
391
if response == ('no',):
392
raise errors.NotBranchError(path=self.root_transport.base)
394
def _ensure_real(self):
395
"""Ensure that there is a _real_bzrdir set.
397
Used before calls to self._real_bzrdir.
399
if not self._real_bzrdir:
400
if 'hpssvfs' in debug.debug_flags:
402
warning('VFS BzrDir access triggered\n%s',
403
''.join(traceback.format_stack()))
404
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
405
self.root_transport, _server_formats=False)
406
self._format._network_name = \
407
self._real_bzrdir._format.network_name()
409
def _translate_error(self, err, **context):
410
_translate_error(err, bzrdir=self, **context)
412
def break_lock(self):
413
# Prevent aliasing problems in the next_open_branch_result cache.
414
# See create_branch for rationale.
415
self._next_open_branch_result = None
416
return _mod_bzrdir.BzrDir.break_lock(self)
418
def _vfs_cloning_metadir(self, require_stacking=False):
420
return self._real_bzrdir.cloning_metadir(
421
require_stacking=require_stacking)
423
def cloning_metadir(self, require_stacking=False):
424
medium = self._client._medium
425
if medium._is_remote_before((1, 13)):
426
return self._vfs_cloning_metadir(require_stacking=require_stacking)
427
verb = 'BzrDir.cloning_metadir'
432
path = self._path_for_remote_call(self._client)
434
response = self._call(verb, path, stacking)
435
except errors.UnknownSmartMethod:
436
medium._remember_remote_is_before((1, 13))
437
return self._vfs_cloning_metadir(require_stacking=require_stacking)
438
except errors.UnknownErrorFromSmartServer, err:
439
if err.error_tuple != ('BranchReference',):
441
# We need to resolve the branch reference to determine the
442
# cloning_metadir. This causes unnecessary RPCs to open the
443
# referenced branch (and bzrdir, etc) but only when the caller
444
# didn't already resolve the branch reference.
445
referenced_branch = self.open_branch()
446
return referenced_branch.bzrdir.cloning_metadir()
447
if len(response) != 3:
448
raise errors.UnexpectedSmartServerResponse(response)
449
control_name, repo_name, branch_info = response
450
if len(branch_info) != 2:
451
raise errors.UnexpectedSmartServerResponse(response)
452
branch_ref, branch_name = branch_info
453
format = controldir.network_format_registry.get(control_name)
455
format.repository_format = _mod_repository.network_format_registry.get(
457
if branch_ref == 'ref':
458
# XXX: we need possible_transports here to avoid reopening the
459
# connection to the referenced location
460
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
461
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
462
format.set_branch_format(branch_format)
463
elif branch_ref == 'branch':
465
format.set_branch_format(
466
branch.network_format_registry.get(branch_name))
468
raise errors.UnexpectedSmartServerResponse(response)
471
def create_repository(self, shared=False):
472
# as per meta1 formats - just delegate to the format object which may
474
result = self._format.repository_format.initialize(self, shared)
475
if not isinstance(result, RemoteRepository):
476
return self.open_repository()
480
def destroy_repository(self):
481
"""See BzrDir.destroy_repository"""
483
self._real_bzrdir.destroy_repository()
485
def create_branch(self, name=None, repository=None):
486
# as per meta1 formats - just delegate to the format object which may
488
real_branch = self._format.get_branch_format().initialize(self,
489
name=name, repository=repository)
490
if not isinstance(real_branch, RemoteBranch):
491
if not isinstance(repository, RemoteRepository):
492
raise AssertionError(
493
'need a RemoteRepository to use with RemoteBranch, got %r'
495
result = RemoteBranch(self, repository, real_branch, name=name)
498
# BzrDir.clone_on_transport() uses the result of create_branch but does
499
# not return it to its callers; we save approximately 8% of our round
500
# trips by handing the branch we created back to the first caller to
501
# open_branch rather than probing anew. Long term we need a API in
502
# bzrdir that doesn't discard result objects (like result_branch).
504
self._next_open_branch_result = result
507
def destroy_branch(self, name=None):
508
"""See BzrDir.destroy_branch"""
510
self._real_bzrdir.destroy_branch(name=name)
511
self._next_open_branch_result = None
513
def create_workingtree(self, revision_id=None, from_branch=None,
514
accelerator_tree=None, hardlink=False):
515
raise errors.NotLocalUrl(self.transport.base)
517
def find_branch_format(self, name=None):
518
"""Find the branch 'format' for this bzrdir.
520
This might be a synthetic object for e.g. RemoteBranch and SVN.
522
b = self.open_branch(name=name)
525
def get_branch_reference(self, name=None):
526
"""See BzrDir.get_branch_reference()."""
528
# XXX JRV20100304: Support opening colocated branches
529
raise errors.NoColocatedBranchSupport(self)
530
response = self._get_branch_reference()
531
if response[0] == 'ref':
536
def _get_branch_reference(self):
537
path = self._path_for_remote_call(self._client)
538
medium = self._client._medium
540
('BzrDir.open_branchV3', (2, 1)),
541
('BzrDir.open_branchV2', (1, 13)),
542
('BzrDir.open_branch', None),
544
for verb, required_version in candidate_calls:
545
if required_version and medium._is_remote_before(required_version):
548
response = self._call(verb, path)
549
except errors.UnknownSmartMethod:
550
if required_version is None:
552
medium._remember_remote_is_before(required_version)
555
if verb == 'BzrDir.open_branch':
556
if response[0] != 'ok':
557
raise errors.UnexpectedSmartServerResponse(response)
558
if response[1] != '':
559
return ('ref', response[1])
561
return ('branch', '')
562
if response[0] not in ('ref', 'branch'):
563
raise errors.UnexpectedSmartServerResponse(response)
566
def _get_tree_branch(self, name=None):
567
"""See BzrDir._get_tree_branch()."""
568
return None, self.open_branch(name=name)
570
def open_branch(self, name=None, unsupported=False,
571
ignore_fallbacks=False):
573
raise NotImplementedError('unsupported flag support not implemented yet.')
574
if self._next_open_branch_result is not None:
575
# See create_branch for details.
576
result = self._next_open_branch_result
577
self._next_open_branch_result = None
579
response = self._get_branch_reference()
580
if response[0] == 'ref':
581
# a branch reference, use the existing BranchReference logic.
582
format = BranchReferenceFormat()
583
return format.open(self, name=name, _found=True,
584
location=response[1], ignore_fallbacks=ignore_fallbacks)
585
branch_format_name = response[1]
586
if not branch_format_name:
587
branch_format_name = None
588
format = RemoteBranchFormat(network_name=branch_format_name)
589
return RemoteBranch(self, self.find_repository(), format=format,
590
setup_stacking=not ignore_fallbacks, name=name)
592
def _open_repo_v1(self, path):
593
verb = 'BzrDir.find_repository'
594
response = self._call(verb, path)
595
if response[0] != 'ok':
596
raise errors.UnexpectedSmartServerResponse(response)
597
# servers that only support the v1 method don't support external
600
repo = self._real_bzrdir.open_repository()
601
response = response + ('no', repo._format.network_name())
602
return response, repo
604
def _open_repo_v2(self, path):
605
verb = 'BzrDir.find_repositoryV2'
606
response = self._call(verb, path)
607
if response[0] != 'ok':
608
raise errors.UnexpectedSmartServerResponse(response)
610
repo = self._real_bzrdir.open_repository()
611
response = response + (repo._format.network_name(),)
612
return response, repo
614
def _open_repo_v3(self, path):
615
verb = 'BzrDir.find_repositoryV3'
616
medium = self._client._medium
617
if medium._is_remote_before((1, 13)):
618
raise errors.UnknownSmartMethod(verb)
620
response = self._call(verb, path)
621
except errors.UnknownSmartMethod:
622
medium._remember_remote_is_before((1, 13))
624
if response[0] != 'ok':
625
raise errors.UnexpectedSmartServerResponse(response)
626
return response, None
628
def open_repository(self):
629
path = self._path_for_remote_call(self._client)
631
for probe in [self._open_repo_v3, self._open_repo_v2,
634
response, real_repo = probe(path)
636
except errors.UnknownSmartMethod:
639
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
640
if response[0] != 'ok':
641
raise errors.UnexpectedSmartServerResponse(response)
642
if len(response) != 6:
643
raise SmartProtocolError('incorrect response length %s' % (response,))
644
if response[1] == '':
645
# repo is at this dir.
646
format = response_tuple_to_repo_format(response[2:])
647
# Used to support creating a real format instance when needed.
648
format._creating_bzrdir = self
649
remote_repo = RemoteRepository(self, format)
650
format._creating_repo = remote_repo
651
if real_repo is not None:
652
remote_repo._set_real_repository(real_repo)
655
raise errors.NoRepositoryPresent(self)
657
def has_workingtree(self):
658
if self._has_working_tree is None:
660
self._has_working_tree = self._real_bzrdir.has_workingtree()
661
return self._has_working_tree
663
def open_workingtree(self, recommend_upgrade=True):
664
if self.has_workingtree():
665
raise errors.NotLocalUrl(self.root_transport)
667
raise errors.NoWorkingTree(self.root_transport.base)
669
def _path_for_remote_call(self, client):
670
"""Return the path to be used for this bzrdir in a remote call."""
671
return client.remote_path_from_transport(self.root_transport)
673
def get_branch_transport(self, branch_format, name=None):
675
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
677
def get_repository_transport(self, repository_format):
679
return self._real_bzrdir.get_repository_transport(repository_format)
681
def get_workingtree_transport(self, workingtree_format):
683
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
685
def can_convert_format(self):
686
"""Upgrading of remote bzrdirs is not supported yet."""
689
def needs_format_conversion(self, format):
690
"""Upgrading of remote bzrdirs is not supported yet."""
693
def clone(self, url, revision_id=None, force_new_repo=False,
694
preserve_stacking=False):
696
return self._real_bzrdir.clone(url, revision_id=revision_id,
697
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
699
def _get_config(self):
700
return RemoteBzrDirConfig(self)
703
class RemoteRepositoryFormat(_mod_repository.RepositoryFormat):
704
"""Format for repositories accessed over a _SmartClient.
706
Instances of this repository are represented by RemoteRepository
709
The RemoteRepositoryFormat is parameterized during construction
710
to reflect the capabilities of the real, remote format. Specifically
711
the attributes rich_root_data and supports_tree_reference are set
712
on a per instance basis, and are not set (and should not be) at
715
:ivar _custom_format: If set, a specific concrete repository format that
716
will be used when initializing a repository with this
717
RemoteRepositoryFormat.
718
:ivar _creating_repo: If set, the repository object that this
719
RemoteRepositoryFormat was created for: it can be called into
720
to obtain data like the network name.
723
_matchingbzrdir = RemoteBzrDirFormat()
724
supports_full_versioned_files = True
725
supports_leaving_lock = True
728
_mod_repository.RepositoryFormat.__init__(self)
729
self._custom_format = None
730
self._network_name = None
731
self._creating_bzrdir = None
732
self._revision_graph_can_have_wrong_parents = None
733
self._supports_chks = None
734
self._supports_external_lookups = None
735
self._supports_tree_reference = None
736
self._supports_funky_characters = None
737
self._rich_root_data = None
740
return "%s(_network_name=%r)" % (self.__class__.__name__,
744
def fast_deltas(self):
746
return self._custom_format.fast_deltas
749
def rich_root_data(self):
750
if self._rich_root_data is None:
752
self._rich_root_data = self._custom_format.rich_root_data
753
return self._rich_root_data
756
def supports_chks(self):
757
if self._supports_chks is None:
759
self._supports_chks = self._custom_format.supports_chks
760
return self._supports_chks
763
def supports_external_lookups(self):
764
if self._supports_external_lookups is None:
766
self._supports_external_lookups = \
767
self._custom_format.supports_external_lookups
768
return self._supports_external_lookups
771
def supports_funky_characters(self):
772
if self._supports_funky_characters is None:
774
self._supports_funky_characters = \
775
self._custom_format.supports_funky_characters
776
return self._supports_funky_characters
779
def supports_tree_reference(self):
780
if self._supports_tree_reference is None:
782
self._supports_tree_reference = \
783
self._custom_format.supports_tree_reference
784
return self._supports_tree_reference
787
def revision_graph_can_have_wrong_parents(self):
788
if self._revision_graph_can_have_wrong_parents is None:
790
self._revision_graph_can_have_wrong_parents = \
791
self._custom_format.revision_graph_can_have_wrong_parents
792
return self._revision_graph_can_have_wrong_parents
794
def _vfs_initialize(self, a_bzrdir, shared):
795
"""Helper for common code in initialize."""
796
if self._custom_format:
797
# Custom format requested
798
result = self._custom_format.initialize(a_bzrdir, shared=shared)
799
elif self._creating_bzrdir is not None:
800
# Use the format that the repository we were created to back
802
prior_repo = self._creating_bzrdir.open_repository()
803
prior_repo._ensure_real()
804
result = prior_repo._real_repository._format.initialize(
805
a_bzrdir, shared=shared)
807
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
808
# support remote initialization.
809
# We delegate to a real object at this point (as RemoteBzrDir
810
# delegate to the repository format which would lead to infinite
811
# recursion if we just called a_bzrdir.create_repository.
812
a_bzrdir._ensure_real()
813
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
814
if not isinstance(result, RemoteRepository):
815
return self.open(a_bzrdir)
819
def initialize(self, a_bzrdir, shared=False):
820
# Being asked to create on a non RemoteBzrDir:
821
if not isinstance(a_bzrdir, RemoteBzrDir):
822
return self._vfs_initialize(a_bzrdir, shared)
823
medium = a_bzrdir._client._medium
824
if medium._is_remote_before((1, 13)):
825
return self._vfs_initialize(a_bzrdir, shared)
826
# Creating on a remote bzr dir.
827
# 1) get the network name to use.
828
if self._custom_format:
829
network_name = self._custom_format.network_name()
830
elif self._network_name:
831
network_name = self._network_name
833
# Select the current bzrlib default and ask for that.
834
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
835
reference_format = reference_bzrdir_format.repository_format
836
network_name = reference_format.network_name()
837
# 2) try direct creation via RPC
838
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
839
verb = 'BzrDir.create_repository'
845
response = a_bzrdir._call(verb, path, network_name, shared_str)
846
except errors.UnknownSmartMethod:
847
# Fallback - use vfs methods
848
medium._remember_remote_is_before((1, 13))
849
return self._vfs_initialize(a_bzrdir, shared)
851
# Turn the response into a RemoteRepository object.
852
format = response_tuple_to_repo_format(response[1:])
853
# Used to support creating a real format instance when needed.
854
format._creating_bzrdir = a_bzrdir
855
remote_repo = RemoteRepository(a_bzrdir, format)
856
format._creating_repo = remote_repo
859
def open(self, a_bzrdir):
860
if not isinstance(a_bzrdir, RemoteBzrDir):
861
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
862
return a_bzrdir.open_repository()
864
def _ensure_real(self):
865
if self._custom_format is None:
866
self._custom_format = _mod_repository.network_format_registry.get(
870
def _fetch_order(self):
872
return self._custom_format._fetch_order
875
def _fetch_uses_deltas(self):
877
return self._custom_format._fetch_uses_deltas
880
def _fetch_reconcile(self):
882
return self._custom_format._fetch_reconcile
884
def get_format_description(self):
886
return 'Remote: ' + self._custom_format.get_format_description()
888
def __eq__(self, other):
889
return self.__class__ is other.__class__
891
def network_name(self):
892
if self._network_name:
893
return self._network_name
894
self._creating_repo._ensure_real()
895
return self._creating_repo._real_repository._format.network_name()
898
def pack_compresses(self):
900
return self._custom_format.pack_compresses
903
def _serializer(self):
905
return self._custom_format._serializer
908
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
909
controldir.ControlComponent):
910
"""Repository accessed over rpc.
912
For the moment most operations are performed using local transport-backed
916
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
917
"""Create a RemoteRepository instance.
919
:param remote_bzrdir: The bzrdir hosting this repository.
920
:param format: The RemoteFormat object to use.
921
:param real_repository: If not None, a local implementation of the
922
repository logic for the repository, usually accessing the data
924
:param _client: Private testing parameter - override the smart client
925
to be used by the repository.
928
self._real_repository = real_repository
930
self._real_repository = None
931
self.bzrdir = remote_bzrdir
933
self._client = remote_bzrdir._client
935
self._client = _client
936
self._format = format
937
self._lock_mode = None
938
self._lock_token = None
940
self._leave_lock = False
941
# Cache of revision parents; misses are cached during read locks, and
942
# write locks when no _real_repository has been set.
943
self._unstacked_provider = graph.CachingParentsProvider(
944
get_parent_map=self._get_parent_map_rpc)
945
self._unstacked_provider.disable_cache()
947
# These depend on the actual remote format, so force them off for
948
# maximum compatibility. XXX: In future these should depend on the
949
# remote repository instance, but this is irrelevant until we perform
950
# reconcile via an RPC call.
951
self._reconcile_does_inventory_gc = False
952
self._reconcile_fixes_text_parents = False
953
self._reconcile_backsup_inventory = False
954
self.base = self.bzrdir.transport.base
955
# Additional places to query for data.
956
self._fallback_repositories = []
959
def user_transport(self):
960
return self.bzrdir.user_transport
963
def control_transport(self):
964
# XXX: Normally you shouldn't directly get at the remote repository
965
# transport, but I'm not sure it's worth making this method
966
# optional -- mbp 2010-04-21
967
return self.bzrdir.get_repository_transport(None)
970
return "%s(%s)" % (self.__class__.__name__, self.base)
974
def abort_write_group(self, suppress_errors=False):
975
"""Complete a write group on the decorated repository.
977
Smart methods perform operations in a single step so this API
978
is not really applicable except as a compatibility thunk
979
for older plugins that don't use e.g. the CommitBuilder
982
:param suppress_errors: see Repository.abort_write_group.
985
return self._real_repository.abort_write_group(
986
suppress_errors=suppress_errors)
990
"""Decorate the real repository for now.
992
In the long term a full blown network facility is needed to avoid
993
creating a real repository object locally.
996
return self._real_repository.chk_bytes
998
def commit_write_group(self):
999
"""Complete a write group on the decorated repository.
1001
Smart methods perform operations in a single step so this API
1002
is not really applicable except as a compatibility thunk
1003
for older plugins that don't use e.g. the CommitBuilder
1007
return self._real_repository.commit_write_group()
1009
def resume_write_group(self, tokens):
1011
return self._real_repository.resume_write_group(tokens)
1013
def suspend_write_group(self):
1015
return self._real_repository.suspend_write_group()
1017
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1019
return self._real_repository.get_missing_parent_inventories(
1020
check_for_missing_texts=check_for_missing_texts)
1022
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1024
return self._real_repository.get_rev_id_for_revno(
1027
def get_rev_id_for_revno(self, revno, known_pair):
1028
"""See Repository.get_rev_id_for_revno."""
1029
path = self.bzrdir._path_for_remote_call(self._client)
1031
if self._client._medium._is_remote_before((1, 17)):
1032
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1033
response = self._call(
1034
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1035
except errors.UnknownSmartMethod:
1036
self._client._medium._remember_remote_is_before((1, 17))
1037
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1038
if response[0] == 'ok':
1039
return True, response[1]
1040
elif response[0] == 'history-incomplete':
1041
known_pair = response[1:3]
1042
for fallback in self._fallback_repositories:
1043
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1048
# Not found in any fallbacks
1049
return False, known_pair
1051
raise errors.UnexpectedSmartServerResponse(response)
1053
def _ensure_real(self):
1054
"""Ensure that there is a _real_repository set.
1056
Used before calls to self._real_repository.
1058
Note that _ensure_real causes many roundtrips to the server which are
1059
not desirable, and prevents the use of smart one-roundtrip RPC's to
1060
perform complex operations (such as accessing parent data, streaming
1061
revisions etc). Adding calls to _ensure_real should only be done when
1062
bringing up new functionality, adding fallbacks for smart methods that
1063
require a fallback path, and never to replace an existing smart method
1064
invocation. If in doubt chat to the bzr network team.
1066
if self._real_repository is None:
1067
if 'hpssvfs' in debug.debug_flags:
1069
warning('VFS Repository access triggered\n%s',
1070
''.join(traceback.format_stack()))
1071
self._unstacked_provider.missing_keys.clear()
1072
self.bzrdir._ensure_real()
1073
self._set_real_repository(
1074
self.bzrdir._real_bzrdir.open_repository())
1076
def _translate_error(self, err, **context):
1077
self.bzrdir._translate_error(err, repository=self, **context)
1079
def find_text_key_references(self):
1080
"""Find the text key references within the repository.
1082
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
1083
revision_ids. Each altered file-ids has the exact revision_ids that
1084
altered it listed explicitly.
1085
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1086
to whether they were referred to by the inventory of the
1087
revision_id that they contain. The inventory texts from all present
1088
revision ids are assessed to generate this report.
1091
return self._real_repository.find_text_key_references()
1093
def _generate_text_key_index(self):
1094
"""Generate a new text key index for the repository.
1096
This is an expensive function that will take considerable time to run.
1098
:return: A dict mapping (file_id, revision_id) tuples to a list of
1099
parents, also (file_id, revision_id) tuples.
1102
return self._real_repository._generate_text_key_index()
1104
def _get_revision_graph(self, revision_id):
1105
"""Private method for using with old (< 1.2) servers to fallback."""
1106
if revision_id is None:
1108
elif _mod_revision.is_null(revision_id):
1111
path = self.bzrdir._path_for_remote_call(self._client)
1112
response = self._call_expecting_body(
1113
'Repository.get_revision_graph', path, revision_id)
1114
response_tuple, response_handler = response
1115
if response_tuple[0] != 'ok':
1116
raise errors.UnexpectedSmartServerResponse(response_tuple)
1117
coded = response_handler.read_body_bytes()
1119
# no revisions in this repository!
1121
lines = coded.split('\n')
1124
d = tuple(line.split())
1125
revision_graph[d[0]] = d[1:]
1127
return revision_graph
1129
def _get_sink(self):
1130
"""See Repository._get_sink()."""
1131
return RemoteStreamSink(self)
1133
def _get_source(self, to_format):
1134
"""Return a source for streaming from this repository."""
1135
return RemoteStreamSource(self, to_format)
1138
def has_revision(self, revision_id):
1139
"""True if this repository has a copy of the revision."""
1140
# Copy of bzrlib.repository.Repository.has_revision
1141
return revision_id in self.has_revisions((revision_id,))
1144
def has_revisions(self, revision_ids):
1145
"""Probe to find out the presence of multiple revisions.
1147
:param revision_ids: An iterable of revision_ids.
1148
:return: A set of the revision_ids that were present.
1150
# Copy of bzrlib.repository.Repository.has_revisions
1151
parent_map = self.get_parent_map(revision_ids)
1152
result = set(parent_map)
1153
if _mod_revision.NULL_REVISION in revision_ids:
1154
result.add(_mod_revision.NULL_REVISION)
1157
def _has_same_fallbacks(self, other_repo):
1158
"""Returns true if the repositories have the same fallbacks."""
1159
# XXX: copied from Repository; it should be unified into a base class
1160
# <https://bugs.launchpad.net/bzr/+bug/401622>
1161
my_fb = self._fallback_repositories
1162
other_fb = other_repo._fallback_repositories
1163
if len(my_fb) != len(other_fb):
1165
for f, g in zip(my_fb, other_fb):
1166
if not f.has_same_location(g):
1170
def has_same_location(self, other):
1171
# TODO: Move to RepositoryBase and unify with the regular Repository
1172
# one; unfortunately the tests rely on slightly different behaviour at
1173
# present -- mbp 20090710
1174
return (self.__class__ is other.__class__ and
1175
self.bzrdir.transport.base == other.bzrdir.transport.base)
1177
def get_graph(self, other_repository=None):
1178
"""Return the graph for this repository format"""
1179
parents_provider = self._make_parents_provider(other_repository)
1180
return graph.Graph(parents_provider)
1183
def get_known_graph_ancestry(self, revision_ids):
1184
"""Return the known graph for a set of revision ids and their ancestors.
1186
st = static_tuple.StaticTuple
1187
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1188
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1189
return graph.GraphThunkIdsToKeys(known_graph)
1191
def gather_stats(self, revid=None, committers=None):
1192
"""See Repository.gather_stats()."""
1193
path = self.bzrdir._path_for_remote_call(self._client)
1194
# revid can be None to indicate no revisions, not just NULL_REVISION
1195
if revid is None or _mod_revision.is_null(revid):
1199
if committers is None or not committers:
1200
fmt_committers = 'no'
1202
fmt_committers = 'yes'
1203
response_tuple, response_handler = self._call_expecting_body(
1204
'Repository.gather_stats', path, fmt_revid, fmt_committers)
1205
if response_tuple[0] != 'ok':
1206
raise errors.UnexpectedSmartServerResponse(response_tuple)
1208
body = response_handler.read_body_bytes()
1210
for line in body.split('\n'):
1213
key, val_text = line.split(':')
1214
if key in ('revisions', 'size', 'committers'):
1215
result[key] = int(val_text)
1216
elif key in ('firstrev', 'latestrev'):
1217
values = val_text.split(' ')[1:]
1218
result[key] = (float(values[0]), long(values[1]))
1222
def find_branches(self, using=False):
1223
"""See Repository.find_branches()."""
1224
# should be an API call to the server.
1226
return self._real_repository.find_branches(using=using)
1228
def get_physical_lock_status(self):
1229
"""See Repository.get_physical_lock_status()."""
1230
# should be an API call to the server.
1232
return self._real_repository.get_physical_lock_status()
1234
def is_in_write_group(self):
1235
"""Return True if there is an open write group.
1237
write groups are only applicable locally for the smart server..
1239
if self._real_repository:
1240
return self._real_repository.is_in_write_group()
1242
def is_locked(self):
1243
return self._lock_count >= 1
1245
def is_shared(self):
1246
"""See Repository.is_shared()."""
1247
path = self.bzrdir._path_for_remote_call(self._client)
1248
response = self._call('Repository.is_shared', path)
1249
if response[0] not in ('yes', 'no'):
1250
raise SmartProtocolError('unexpected response code %s' % (response,))
1251
return response[0] == 'yes'
1253
def is_write_locked(self):
1254
return self._lock_mode == 'w'
1256
def _warn_if_deprecated(self, branch=None):
1257
# If we have a real repository, the check will be done there, if we
1258
# don't the check will be done remotely.
1261
def lock_read(self):
1262
"""Lock the repository for read operations.
1264
:return: A bzrlib.lock.LogicalLockResult.
1266
# wrong eventually - want a local lock cache context
1267
if not self._lock_mode:
1268
self._note_lock('r')
1269
self._lock_mode = 'r'
1270
self._lock_count = 1
1271
self._unstacked_provider.enable_cache(cache_misses=True)
1272
if self._real_repository is not None:
1273
self._real_repository.lock_read()
1274
for repo in self._fallback_repositories:
1277
self._lock_count += 1
1278
return lock.LogicalLockResult(self.unlock)
1280
def _remote_lock_write(self, token):
1281
path = self.bzrdir._path_for_remote_call(self._client)
1284
err_context = {'token': token}
1285
response = self._call('Repository.lock_write', path, token,
1287
if response[0] == 'ok':
1288
ok, token = response
1291
raise errors.UnexpectedSmartServerResponse(response)
1293
def lock_write(self, token=None, _skip_rpc=False):
1294
if not self._lock_mode:
1295
self._note_lock('w')
1297
if self._lock_token is not None:
1298
if token != self._lock_token:
1299
raise errors.TokenMismatch(token, self._lock_token)
1300
self._lock_token = token
1302
self._lock_token = self._remote_lock_write(token)
1303
# if self._lock_token is None, then this is something like packs or
1304
# svn where we don't get to lock the repo, or a weave style repository
1305
# where we cannot lock it over the wire and attempts to do so will
1307
if self._real_repository is not None:
1308
self._real_repository.lock_write(token=self._lock_token)
1309
if token is not None:
1310
self._leave_lock = True
1312
self._leave_lock = False
1313
self._lock_mode = 'w'
1314
self._lock_count = 1
1315
cache_misses = self._real_repository is None
1316
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1317
for repo in self._fallback_repositories:
1318
# Writes don't affect fallback repos
1320
elif self._lock_mode == 'r':
1321
raise errors.ReadOnlyError(self)
1323
self._lock_count += 1
1324
return RepositoryWriteLockResult(self.unlock, self._lock_token or None)
1326
def leave_lock_in_place(self):
1327
if not self._lock_token:
1328
raise NotImplementedError(self.leave_lock_in_place)
1329
self._leave_lock = True
1331
def dont_leave_lock_in_place(self):
1332
if not self._lock_token:
1333
raise NotImplementedError(self.dont_leave_lock_in_place)
1334
self._leave_lock = False
1336
def _set_real_repository(self, repository):
1337
"""Set the _real_repository for this repository.
1339
:param repository: The repository to fallback to for non-hpss
1340
implemented operations.
1342
if self._real_repository is not None:
1343
# Replacing an already set real repository.
1344
# We cannot do this [currently] if the repository is locked -
1345
# synchronised state might be lost.
1346
if self.is_locked():
1347
raise AssertionError('_real_repository is already set')
1348
if isinstance(repository, RemoteRepository):
1349
raise AssertionError()
1350
self._real_repository = repository
1351
# three code paths happen here:
1352
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1353
# up stacking. In this case self._fallback_repositories is [], and the
1354
# real repo is already setup. Preserve the real repo and
1355
# RemoteRepository.add_fallback_repository will avoid adding
1357
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1358
# ensure_real is triggered from a branch, the real repository to
1359
# set already has a matching list with separate instances, but
1360
# as they are also RemoteRepositories we don't worry about making the
1361
# lists be identical.
1362
# 3) new servers, RemoteRepository.ensure_real is triggered before
1363
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1364
# and need to populate it.
1365
if (self._fallback_repositories and
1366
len(self._real_repository._fallback_repositories) !=
1367
len(self._fallback_repositories)):
1368
if len(self._real_repository._fallback_repositories):
1369
raise AssertionError(
1370
"cannot cleanly remove existing _fallback_repositories")
1371
for fb in self._fallback_repositories:
1372
self._real_repository.add_fallback_repository(fb)
1373
if self._lock_mode == 'w':
1374
# if we are already locked, the real repository must be able to
1375
# acquire the lock with our token.
1376
self._real_repository.lock_write(self._lock_token)
1377
elif self._lock_mode == 'r':
1378
self._real_repository.lock_read()
1380
def start_write_group(self):
1381
"""Start a write group on the decorated repository.
1383
Smart methods perform operations in a single step so this API
1384
is not really applicable except as a compatibility thunk
1385
for older plugins that don't use e.g. the CommitBuilder
1389
return self._real_repository.start_write_group()
1391
def _unlock(self, token):
1392
path = self.bzrdir._path_for_remote_call(self._client)
1394
# with no token the remote repository is not persistently locked.
1396
err_context = {'token': token}
1397
response = self._call('Repository.unlock', path, token,
1399
if response == ('ok',):
1402
raise errors.UnexpectedSmartServerResponse(response)
1404
@only_raises(errors.LockNotHeld, errors.LockBroken)
1406
if not self._lock_count:
1407
return lock.cant_unlock_not_held(self)
1408
self._lock_count -= 1
1409
if self._lock_count > 0:
1411
self._unstacked_provider.disable_cache()
1412
old_mode = self._lock_mode
1413
self._lock_mode = None
1415
# The real repository is responsible at present for raising an
1416
# exception if it's in an unfinished write group. However, it
1417
# normally will *not* actually remove the lock from disk - that's
1418
# done by the server on receiving the Repository.unlock call.
1419
# This is just to let the _real_repository stay up to date.
1420
if self._real_repository is not None:
1421
self._real_repository.unlock()
1423
# The rpc-level lock should be released even if there was a
1424
# problem releasing the vfs-based lock.
1426
# Only write-locked repositories need to make a remote method
1427
# call to perform the unlock.
1428
old_token = self._lock_token
1429
self._lock_token = None
1430
if not self._leave_lock:
1431
self._unlock(old_token)
1432
# Fallbacks are always 'lock_read()' so we don't pay attention to
1434
for repo in self._fallback_repositories:
1437
def break_lock(self):
1438
# should hand off to the network
1440
return self._real_repository.break_lock()
1442
def _get_tarball(self, compression):
1443
"""Return a TemporaryFile containing a repository tarball.
1445
Returns None if the server does not support sending tarballs.
1448
path = self.bzrdir._path_for_remote_call(self._client)
1450
response, protocol = self._call_expecting_body(
1451
'Repository.tarball', path, compression)
1452
except errors.UnknownSmartMethod:
1453
protocol.cancel_read_body()
1455
if response[0] == 'ok':
1456
# Extract the tarball and return it
1457
t = tempfile.NamedTemporaryFile()
1458
# TODO: rpc layer should read directly into it...
1459
t.write(protocol.read_body_bytes())
1462
raise errors.UnexpectedSmartServerResponse(response)
1464
def sprout(self, to_bzrdir, revision_id=None):
1465
# TODO: Option to control what format is created?
1467
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1469
dest_repo.fetch(self, revision_id=revision_id)
1472
### These methods are just thin shims to the VFS object for now.
1474
def revision_tree(self, revision_id):
1476
return self._real_repository.revision_tree(revision_id)
1478
def get_serializer_format(self):
1480
return self._real_repository.get_serializer_format()
1482
def get_commit_builder(self, branch, parents, config, timestamp=None,
1483
timezone=None, committer=None, revprops=None,
1484
revision_id=None, lossy=False):
1485
# FIXME: It ought to be possible to call this without immediately
1486
# triggering _ensure_real. For now it's the easiest thing to do.
1488
real_repo = self._real_repository
1489
builder = real_repo.get_commit_builder(branch, parents,
1490
config, timestamp=timestamp, timezone=timezone,
1491
committer=committer, revprops=revprops,
1492
revision_id=revision_id, lossy=lossy)
1495
def add_fallback_repository(self, repository):
1496
"""Add a repository to use for looking up data not held locally.
1498
:param repository: A repository.
1500
if not self._format.supports_external_lookups:
1501
raise errors.UnstackableRepositoryFormat(
1502
self._format.network_name(), self.base)
1503
# We need to accumulate additional repositories here, to pass them in
1506
if self.is_locked():
1507
# We will call fallback.unlock() when we transition to the unlocked
1508
# state, so always add a lock here. If a caller passes us a locked
1509
# repository, they are responsible for unlocking it later.
1510
repository.lock_read()
1511
self._check_fallback_repository(repository)
1512
self._fallback_repositories.append(repository)
1513
# If self._real_repository was parameterised already (e.g. because a
1514
# _real_branch had its get_stacked_on_url method called), then the
1515
# repository to be added may already be in the _real_repositories list.
1516
if self._real_repository is not None:
1517
fallback_locations = [repo.user_url for repo in
1518
self._real_repository._fallback_repositories]
1519
if repository.user_url not in fallback_locations:
1520
self._real_repository.add_fallback_repository(repository)
1522
def _check_fallback_repository(self, repository):
1523
"""Check that this repository can fallback to repository safely.
1525
Raise an error if not.
1527
:param repository: A repository to fallback to.
1529
return _mod_repository.InterRepository._assert_same_model(
1532
def add_inventory(self, revid, inv, parents):
1534
return self._real_repository.add_inventory(revid, inv, parents)
1536
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1537
parents, basis_inv=None, propagate_caches=False):
1539
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1540
delta, new_revision_id, parents, basis_inv=basis_inv,
1541
propagate_caches=propagate_caches)
1543
def add_revision(self, rev_id, rev, inv=None, config=None):
1545
return self._real_repository.add_revision(
1546
rev_id, rev, inv=inv, config=config)
1549
def get_inventory(self, revision_id):
1551
return self._real_repository.get_inventory(revision_id)
1553
def iter_inventories(self, revision_ids, ordering=None):
1555
return self._real_repository.iter_inventories(revision_ids, ordering)
1558
def get_revision(self, revision_id):
1560
return self._real_repository.get_revision(revision_id)
1562
def get_transaction(self):
1564
return self._real_repository.get_transaction()
1567
def clone(self, a_bzrdir, revision_id=None):
1569
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1571
def make_working_trees(self):
1572
"""See Repository.make_working_trees"""
1574
return self._real_repository.make_working_trees()
1576
def refresh_data(self):
1577
"""Re-read any data needed to synchronise with disk.
1579
This method is intended to be called after another repository instance
1580
(such as one used by a smart server) has inserted data into the
1581
repository. On all repositories this will work outside of write groups.
1582
Some repository formats (pack and newer for bzrlib native formats)
1583
support refresh_data inside write groups. If called inside a write
1584
group on a repository that does not support refreshing in a write group
1585
IsInWriteGroupError will be raised.
1587
if self._real_repository is not None:
1588
self._real_repository.refresh_data()
1590
def revision_ids_to_search_result(self, result_set):
1591
"""Convert a set of revision ids to a graph SearchResult."""
1592
result_parents = set()
1593
for parents in self.get_graph().get_parent_map(
1594
result_set).itervalues():
1595
result_parents.update(parents)
1596
included_keys = result_set.intersection(result_parents)
1597
start_keys = result_set.difference(included_keys)
1598
exclude_keys = result_parents.difference(result_set)
1599
result = graph.SearchResult(start_keys, exclude_keys,
1600
len(result_set), result_set)
1604
def search_missing_revision_ids(self, other,
1605
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1606
find_ghosts=True, revision_ids=None, if_present_ids=None):
1607
"""Return the revision ids that other has that this does not.
1609
These are returned in topological order.
1611
revision_id: only return revision ids included by revision_id.
1613
if symbol_versioning.deprecated_passed(revision_id):
1614
symbol_versioning.warn(
1615
'search_missing_revision_ids(revision_id=...) was '
1616
'deprecated in 2.4. Use revision_ids=[...] instead.',
1617
DeprecationWarning, stacklevel=2)
1618
if revision_ids is not None:
1619
raise AssertionError(
1620
'revision_ids is mutually exclusive with revision_id')
1621
if revision_id is not None:
1622
revision_ids = [revision_id]
1623
inter_repo = _mod_repository.InterRepository.get(other, self)
1624
return inter_repo.search_missing_revision_ids(
1625
find_ghosts=find_ghosts, revision_ids=revision_ids,
1626
if_present_ids=if_present_ids)
1628
def fetch(self, source, revision_id=None, find_ghosts=False,
1630
# No base implementation to use as RemoteRepository is not a subclass
1631
# of Repository; so this is a copy of Repository.fetch().
1632
if fetch_spec is not None and revision_id is not None:
1633
raise AssertionError(
1634
"fetch_spec and revision_id are mutually exclusive.")
1635
if self.is_in_write_group():
1636
raise errors.InternalBzrError(
1637
"May not fetch while in a write group.")
1638
# fast path same-url fetch operations
1639
if (self.has_same_location(source)
1640
and fetch_spec is None
1641
and self._has_same_fallbacks(source)):
1642
# check that last_revision is in 'from' and then return a
1644
if (revision_id is not None and
1645
not _mod_revision.is_null(revision_id)):
1646
self.get_revision(revision_id)
1648
# if there is no specific appropriate InterRepository, this will get
1649
# the InterRepository base class, which raises an
1650
# IncompatibleRepositories when asked to fetch.
1651
inter = _mod_repository.InterRepository.get(source, self)
1652
return inter.fetch(revision_id=revision_id,
1653
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1655
def create_bundle(self, target, base, fileobj, format=None):
1657
self._real_repository.create_bundle(target, base, fileobj, format)
1660
def get_ancestry(self, revision_id, topo_sorted=True):
1662
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1664
def fileids_altered_by_revision_ids(self, revision_ids):
1666
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1668
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1670
return self._real_repository._get_versioned_file_checker(
1671
revisions, revision_versions_cache)
1673
def iter_files_bytes(self, desired_files):
1674
"""See Repository.iter_file_bytes.
1677
return self._real_repository.iter_files_bytes(desired_files)
1679
def get_parent_map(self, revision_ids):
1680
"""See bzrlib.Graph.get_parent_map()."""
1681
return self._make_parents_provider().get_parent_map(revision_ids)
1683
def _get_parent_map_rpc(self, keys):
1684
"""Helper for get_parent_map that performs the RPC."""
1685
medium = self._client._medium
1686
if medium._is_remote_before((1, 2)):
1687
# We already found out that the server can't understand
1688
# Repository.get_parent_map requests, so just fetch the whole
1691
# Note that this reads the whole graph, when only some keys are
1692
# wanted. On this old server there's no way (?) to get them all
1693
# in one go, and the user probably will have seen a warning about
1694
# the server being old anyhow.
1695
rg = self._get_revision_graph(None)
1696
# There is an API discrepancy between get_parent_map and
1697
# get_revision_graph. Specifically, a "key:()" pair in
1698
# get_revision_graph just means a node has no parents. For
1699
# "get_parent_map" it means the node is a ghost. So fix up the
1700
# graph to correct this.
1701
# https://bugs.launchpad.net/bzr/+bug/214894
1702
# There is one other "bug" which is that ghosts in
1703
# get_revision_graph() are not returned at all. But we won't worry
1704
# about that for now.
1705
for node_id, parent_ids in rg.iteritems():
1706
if parent_ids == ():
1707
rg[node_id] = (NULL_REVISION,)
1708
rg[NULL_REVISION] = ()
1713
raise ValueError('get_parent_map(None) is not valid')
1714
if NULL_REVISION in keys:
1715
keys.discard(NULL_REVISION)
1716
found_parents = {NULL_REVISION:()}
1718
return found_parents
1721
# TODO(Needs analysis): We could assume that the keys being requested
1722
# from get_parent_map are in a breadth first search, so typically they
1723
# will all be depth N from some common parent, and we don't have to
1724
# have the server iterate from the root parent, but rather from the
1725
# keys we're searching; and just tell the server the keyspace we
1726
# already have; but this may be more traffic again.
1728
# Transform self._parents_map into a search request recipe.
1729
# TODO: Manage this incrementally to avoid covering the same path
1730
# repeatedly. (The server will have to on each request, but the less
1731
# work done the better).
1733
# Negative caching notes:
1734
# new server sends missing when a request including the revid
1735
# 'include-missing:' is present in the request.
1736
# missing keys are serialised as missing:X, and we then call
1737
# provider.note_missing(X) for-all X
1738
parents_map = self._unstacked_provider.get_cached_map()
1739
if parents_map is None:
1740
# Repository is not locked, so there's no cache.
1742
# start_set is all the keys in the cache
1743
start_set = set(parents_map)
1744
# result set is all the references to keys in the cache
1745
result_parents = set()
1746
for parents in parents_map.itervalues():
1747
result_parents.update(parents)
1748
stop_keys = result_parents.difference(start_set)
1749
# We don't need to send ghosts back to the server as a position to
1751
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1752
key_count = len(parents_map)
1753
if (NULL_REVISION in result_parents
1754
and NULL_REVISION in self._unstacked_provider.missing_keys):
1755
# If we pruned NULL_REVISION from the stop_keys because it's also
1756
# in our cache of "missing" keys we need to increment our key count
1757
# by 1, because the reconsitituted SearchResult on the server will
1758
# still consider NULL_REVISION to be an included key.
1760
included_keys = start_set.intersection(result_parents)
1761
start_set.difference_update(included_keys)
1762
recipe = ('manual', start_set, stop_keys, key_count)
1763
body = self._serialise_search_recipe(recipe)
1764
path = self.bzrdir._path_for_remote_call(self._client)
1766
if type(key) is not str:
1768
"key %r not a plain string" % (key,))
1769
verb = 'Repository.get_parent_map'
1770
args = (path, 'include-missing:') + tuple(keys)
1772
response = self._call_with_body_bytes_expecting_body(
1774
except errors.UnknownSmartMethod:
1775
# Server does not support this method, so get the whole graph.
1776
# Worse, we have to force a disconnection, because the server now
1777
# doesn't realise it has a body on the wire to consume, so the
1778
# only way to recover is to abandon the connection.
1780
'Server is too old for fast get_parent_map, reconnecting. '
1781
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1783
# To avoid having to disconnect repeatedly, we keep track of the
1784
# fact the server doesn't understand remote methods added in 1.2.
1785
medium._remember_remote_is_before((1, 2))
1786
# Recurse just once and we should use the fallback code.
1787
return self._get_parent_map_rpc(keys)
1788
response_tuple, response_handler = response
1789
if response_tuple[0] not in ['ok']:
1790
response_handler.cancel_read_body()
1791
raise errors.UnexpectedSmartServerResponse(response_tuple)
1792
if response_tuple[0] == 'ok':
1793
coded = bz2.decompress(response_handler.read_body_bytes())
1795
# no revisions found
1797
lines = coded.split('\n')
1800
d = tuple(line.split())
1802
revision_graph[d[0]] = d[1:]
1805
if d[0].startswith('missing:'):
1807
self._unstacked_provider.note_missing_key(revid)
1809
# no parents - so give the Graph result
1811
revision_graph[d[0]] = (NULL_REVISION,)
1812
return revision_graph
1815
def get_signature_text(self, revision_id):
1817
return self._real_repository.get_signature_text(revision_id)
1820
def _get_inventory_xml(self, revision_id):
1822
return self._real_repository._get_inventory_xml(revision_id)
1824
def reconcile(self, other=None, thorough=False):
1826
return self._real_repository.reconcile(other=other, thorough=thorough)
1828
def all_revision_ids(self):
1830
return self._real_repository.all_revision_ids()
1833
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1835
return self._real_repository.get_deltas_for_revisions(revisions,
1836
specific_fileids=specific_fileids)
1839
def get_revision_delta(self, revision_id, specific_fileids=None):
1841
return self._real_repository.get_revision_delta(revision_id,
1842
specific_fileids=specific_fileids)
1845
def revision_trees(self, revision_ids):
1847
return self._real_repository.revision_trees(revision_ids)
1850
def get_revision_reconcile(self, revision_id):
1852
return self._real_repository.get_revision_reconcile(revision_id)
1855
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1857
return self._real_repository.check(revision_ids=revision_ids,
1858
callback_refs=callback_refs, check_repo=check_repo)
1860
def copy_content_into(self, destination, revision_id=None):
1862
return self._real_repository.copy_content_into(
1863
destination, revision_id=revision_id)
1865
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1866
# get a tarball of the remote repository, and copy from that into the
1868
from bzrlib import osutils
1870
# TODO: Maybe a progress bar while streaming the tarball?
1871
note("Copying repository content as tarball...")
1872
tar_file = self._get_tarball('bz2')
1873
if tar_file is None:
1875
destination = to_bzrdir.create_repository()
1877
tar = tarfile.open('repository', fileobj=tar_file,
1879
tmpdir = osutils.mkdtemp()
1881
_extract_tar(tar, tmpdir)
1882
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
1883
tmp_repo = tmp_bzrdir.open_repository()
1884
tmp_repo.copy_content_into(destination, revision_id)
1886
osutils.rmtree(tmpdir)
1890
# TODO: Suggestion from john: using external tar is much faster than
1891
# python's tarfile library, but it may not work on windows.
1894
def inventories(self):
1895
"""Decorate the real repository for now.
1897
In the long term a full blown network facility is needed to
1898
avoid creating a real repository object locally.
1901
return self._real_repository.inventories
1904
def pack(self, hint=None, clean_obsolete_packs=False):
1905
"""Compress the data within the repository.
1907
This is not currently implemented within the smart server.
1910
return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1913
def revisions(self):
1914
"""Decorate the real repository for now.
1916
In the short term this should become a real object to intercept graph
1919
In the long term a full blown network facility is needed.
1922
return self._real_repository.revisions
1924
def set_make_working_trees(self, new_value):
1926
new_value_str = "True"
1928
new_value_str = "False"
1929
path = self.bzrdir._path_for_remote_call(self._client)
1931
response = self._call(
1932
'Repository.set_make_working_trees', path, new_value_str)
1933
except errors.UnknownSmartMethod:
1935
self._real_repository.set_make_working_trees(new_value)
1937
if response[0] != 'ok':
1938
raise errors.UnexpectedSmartServerResponse(response)
1941
def signatures(self):
1942
"""Decorate the real repository for now.
1944
In the long term a full blown network facility is needed to avoid
1945
creating a real repository object locally.
1948
return self._real_repository.signatures
1951
def sign_revision(self, revision_id, gpg_strategy):
1953
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1957
"""Decorate the real repository for now.
1959
In the long term a full blown network facility is needed to avoid
1960
creating a real repository object locally.
1963
return self._real_repository.texts
1966
def get_revisions(self, revision_ids):
1968
return self._real_repository.get_revisions(revision_ids)
1970
def supports_rich_root(self):
1971
return self._format.rich_root_data
1973
def iter_reverse_revision_history(self, revision_id):
1975
return self._real_repository.iter_reverse_revision_history(revision_id)
1978
def _serializer(self):
1979
return self._format._serializer
1981
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1983
return self._real_repository.store_revision_signature(
1984
gpg_strategy, plaintext, revision_id)
1986
def add_signature_text(self, revision_id, signature):
1988
return self._real_repository.add_signature_text(revision_id, signature)
1990
def has_signature_for_revision_id(self, revision_id):
1992
return self._real_repository.has_signature_for_revision_id(revision_id)
1994
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1996
return self._real_repository.item_keys_introduced_by(revision_ids,
1997
_files_pb=_files_pb)
1999
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2001
return self._real_repository._find_inconsistent_revision_parents(
2004
def _check_for_inconsistent_revision_parents(self):
2006
return self._real_repository._check_for_inconsistent_revision_parents()
2008
def _make_parents_provider(self, other=None):
2009
providers = [self._unstacked_provider]
2010
if other is not None:
2011
providers.insert(0, other)
2012
providers.extend(r._make_parents_provider() for r in
2013
self._fallback_repositories)
2014
return graph.StackedParentsProvider(providers)
2016
def _serialise_search_recipe(self, recipe):
2017
"""Serialise a graph search recipe.
2019
:param recipe: A search recipe (start, stop, count).
2020
:return: Serialised bytes.
2022
start_keys = ' '.join(recipe[1])
2023
stop_keys = ' '.join(recipe[2])
2024
count = str(recipe[3])
2025
return '\n'.join((start_keys, stop_keys, count))
2027
def _serialise_search_result(self, search_result):
2028
parts = search_result.get_network_struct()
2029
return '\n'.join(parts)
2032
path = self.bzrdir._path_for_remote_call(self._client)
2034
response = self._call('PackRepository.autopack', path)
2035
except errors.UnknownSmartMethod:
2037
self._real_repository._pack_collection.autopack()
2040
if response[0] != 'ok':
2041
raise errors.UnexpectedSmartServerResponse(response)
2044
class RemoteStreamSink(_mod_repository.StreamSink):
2046
def _insert_real(self, stream, src_format, resume_tokens):
2047
self.target_repo._ensure_real()
2048
sink = self.target_repo._real_repository._get_sink()
2049
result = sink.insert_stream(stream, src_format, resume_tokens)
2051
self.target_repo.autopack()
2054
def insert_stream(self, stream, src_format, resume_tokens):
2055
target = self.target_repo
2056
target._unstacked_provider.missing_keys.clear()
2057
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2058
if target._lock_token:
2059
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2060
lock_args = (target._lock_token or '',)
2062
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2064
client = target._client
2065
medium = client._medium
2066
path = target.bzrdir._path_for_remote_call(client)
2067
# Probe for the verb to use with an empty stream before sending the
2068
# real stream to it. We do this both to avoid the risk of sending a
2069
# large request that is then rejected, and because we don't want to
2070
# implement a way to buffer, rewind, or restart the stream.
2072
for verb, required_version in candidate_calls:
2073
if medium._is_remote_before(required_version):
2076
# We've already done the probing (and set _is_remote_before) on
2077
# a previous insert.
2080
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2082
response = client.call_with_body_stream(
2083
(verb, path, '') + lock_args, byte_stream)
2084
except errors.UnknownSmartMethod:
2085
medium._remember_remote_is_before(required_version)
2091
return self._insert_real(stream, src_format, resume_tokens)
2092
self._last_inv_record = None
2093
self._last_substream = None
2094
if required_version < (1, 19):
2095
# Remote side doesn't support inventory deltas. Wrap the stream to
2096
# make sure we don't send any. If the stream contains inventory
2097
# deltas we'll interrupt the smart insert_stream request and
2099
stream = self._stop_stream_if_inventory_delta(stream)
2100
byte_stream = smart_repo._stream_to_byte_stream(
2102
resume_tokens = ' '.join(resume_tokens)
2103
response = client.call_with_body_stream(
2104
(verb, path, resume_tokens) + lock_args, byte_stream)
2105
if response[0][0] not in ('ok', 'missing-basis'):
2106
raise errors.UnexpectedSmartServerResponse(response)
2107
if self._last_substream is not None:
2108
# The stream included an inventory-delta record, but the remote
2109
# side isn't new enough to support them. So we need to send the
2110
# rest of the stream via VFS.
2111
self.target_repo.refresh_data()
2112
return self._resume_stream_with_vfs(response, src_format)
2113
if response[0][0] == 'missing-basis':
2114
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2115
resume_tokens = tokens
2116
return resume_tokens, set(missing_keys)
2118
self.target_repo.refresh_data()
2121
def _resume_stream_with_vfs(self, response, src_format):
2122
"""Resume sending a stream via VFS, first resending the record and
2123
substream that couldn't be sent via an insert_stream verb.
2125
if response[0][0] == 'missing-basis':
2126
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2127
# Ignore missing_keys, we haven't finished inserting yet
2130
def resume_substream():
2131
# Yield the substream that was interrupted.
2132
for record in self._last_substream:
2134
self._last_substream = None
2135
def resume_stream():
2136
# Finish sending the interrupted substream
2137
yield ('inventory-deltas', resume_substream())
2138
# Then simply continue sending the rest of the stream.
2139
for substream_kind, substream in self._last_stream:
2140
yield substream_kind, substream
2141
return self._insert_real(resume_stream(), src_format, tokens)
2143
def _stop_stream_if_inventory_delta(self, stream):
2144
"""Normally this just lets the original stream pass-through unchanged.
2146
However if any 'inventory-deltas' substream occurs it will stop
2147
streaming, and store the interrupted substream and stream in
2148
self._last_substream and self._last_stream so that the stream can be
2149
resumed by _resume_stream_with_vfs.
2152
stream_iter = iter(stream)
2153
for substream_kind, substream in stream_iter:
2154
if substream_kind == 'inventory-deltas':
2155
self._last_substream = substream
2156
self._last_stream = stream_iter
2159
yield substream_kind, substream
2162
class RemoteStreamSource(_mod_repository.StreamSource):
2163
"""Stream data from a remote server."""
2165
def get_stream(self, search):
2166
if (self.from_repository._fallback_repositories and
2167
self.to_format._fetch_order == 'topological'):
2168
return self._real_stream(self.from_repository, search)
2171
repos = [self.from_repository]
2177
repos.extend(repo._fallback_repositories)
2178
sources.append(repo)
2179
return self.missing_parents_chain(search, sources)
2181
def get_stream_for_missing_keys(self, missing_keys):
2182
self.from_repository._ensure_real()
2183
real_repo = self.from_repository._real_repository
2184
real_source = real_repo._get_source(self.to_format)
2185
return real_source.get_stream_for_missing_keys(missing_keys)
2187
def _real_stream(self, repo, search):
2188
"""Get a stream for search from repo.
2190
This never called RemoteStreamSource.get_stream, and is a heler
2191
for RemoteStreamSource._get_stream to allow getting a stream
2192
reliably whether fallback back because of old servers or trying
2193
to stream from a non-RemoteRepository (which the stacked support
2196
source = repo._get_source(self.to_format)
2197
if isinstance(source, RemoteStreamSource):
2199
source = repo._real_repository._get_source(self.to_format)
2200
return source.get_stream(search)
2202
def _get_stream(self, repo, search):
2203
"""Core worker to get a stream from repo for search.
2205
This is used by both get_stream and the stacking support logic. It
2206
deliberately gets a stream for repo which does not need to be
2207
self.from_repository. In the event that repo is not Remote, or
2208
cannot do a smart stream, a fallback is made to the generic
2209
repository._get_stream() interface, via self._real_stream.
2211
In the event of stacking, streams from _get_stream will not
2212
contain all the data for search - this is normal (see get_stream).
2214
:param repo: A repository.
2215
:param search: A search.
2217
# Fallbacks may be non-smart
2218
if not isinstance(repo, RemoteRepository):
2219
return self._real_stream(repo, search)
2220
client = repo._client
2221
medium = client._medium
2222
path = repo.bzrdir._path_for_remote_call(client)
2223
search_bytes = repo._serialise_search_result(search)
2224
args = (path, self.to_format.network_name())
2226
('Repository.get_stream_1.19', (1, 19)),
2227
('Repository.get_stream', (1, 13))]
2230
for verb, version in candidate_verbs:
2231
if medium._is_remote_before(version):
2234
response = repo._call_with_body_bytes_expecting_body(
2235
verb, args, search_bytes)
2236
except errors.UnknownSmartMethod:
2237
medium._remember_remote_is_before(version)
2238
except errors.UnknownErrorFromSmartServer, e:
2239
if isinstance(search, graph.EverythingResult):
2240
error_verb = e.error_from_smart_server.error_verb
2241
if error_verb == 'BadSearch':
2242
# Pre-2.4 servers don't support this sort of search.
2243
# XXX: perhaps falling back to VFS on BadSearch is a
2244
# good idea in general? It might provide a little bit
2245
# of protection against client-side bugs.
2246
medium._remember_remote_is_before((2, 4))
2250
response_tuple, response_handler = response
2254
return self._real_stream(repo, search)
2255
if response_tuple[0] != 'ok':
2256
raise errors.UnexpectedSmartServerResponse(response_tuple)
2257
byte_stream = response_handler.read_streamed_body()
2258
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2259
self._record_counter)
2260
if src_format.network_name() != repo._format.network_name():
2261
raise AssertionError(
2262
"Mismatched RemoteRepository and stream src %r, %r" % (
2263
src_format.network_name(), repo._format.network_name()))
2266
def missing_parents_chain(self, search, sources):
2267
"""Chain multiple streams together to handle stacking.
2269
:param search: The overall search to satisfy with streams.
2270
:param sources: A list of Repository objects to query.
2272
self.from_serialiser = self.from_repository._format._serializer
2273
self.seen_revs = set()
2274
self.referenced_revs = set()
2275
# If there are heads in the search, or the key count is > 0, we are not
2277
while not search.is_empty() and len(sources) > 1:
2278
source = sources.pop(0)
2279
stream = self._get_stream(source, search)
2280
for kind, substream in stream:
2281
if kind != 'revisions':
2282
yield kind, substream
2284
yield kind, self.missing_parents_rev_handler(substream)
2285
search = search.refine(self.seen_revs, self.referenced_revs)
2286
self.seen_revs = set()
2287
self.referenced_revs = set()
2288
if not search.is_empty():
2289
for kind, stream in self._get_stream(sources[0], search):
2292
def missing_parents_rev_handler(self, substream):
2293
for content in substream:
2294
revision_bytes = content.get_bytes_as('fulltext')
2295
revision = self.from_serialiser.read_revision_from_string(
2297
self.seen_revs.add(content.key[-1])
2298
self.referenced_revs.update(revision.parent_ids)
2302
class RemoteBranchLockableFiles(LockableFiles):
2303
"""A 'LockableFiles' implementation that talks to a smart server.
2305
This is not a public interface class.
2308
def __init__(self, bzrdir, _client):
2309
self.bzrdir = bzrdir
2310
self._client = _client
2311
self._need_find_modes = True
2312
LockableFiles.__init__(
2313
self, bzrdir.get_branch_transport(None),
2314
'lock', lockdir.LockDir)
2316
def _find_modes(self):
2317
# RemoteBranches don't let the client set the mode of control files.
2318
self._dir_mode = None
2319
self._file_mode = None
2322
class RemoteBranchFormat(branch.BranchFormat):
2324
def __init__(self, network_name=None):
2325
super(RemoteBranchFormat, self).__init__()
2326
self._matchingbzrdir = RemoteBzrDirFormat()
2327
self._matchingbzrdir.set_branch_format(self)
2328
self._custom_format = None
2329
self._network_name = network_name
2331
def __eq__(self, other):
2332
return (isinstance(other, RemoteBranchFormat) and
2333
self.__dict__ == other.__dict__)
2335
def _ensure_real(self):
2336
if self._custom_format is None:
2337
self._custom_format = branch.network_format_registry.get(
2340
def get_format_description(self):
2342
return 'Remote: ' + self._custom_format.get_format_description()
2344
def network_name(self):
2345
return self._network_name
2347
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2348
return a_bzrdir.open_branch(name=name,
2349
ignore_fallbacks=ignore_fallbacks)
2351
def _vfs_initialize(self, a_bzrdir, name):
2352
# Initialisation when using a local bzrdir object, or a non-vfs init
2353
# method is not available on the server.
2354
# self._custom_format is always set - the start of initialize ensures
2356
if isinstance(a_bzrdir, RemoteBzrDir):
2357
a_bzrdir._ensure_real()
2358
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2361
# We assume the bzrdir is parameterised; it may not be.
2362
result = self._custom_format.initialize(a_bzrdir, name)
2363
if (isinstance(a_bzrdir, RemoteBzrDir) and
2364
not isinstance(result, RemoteBranch)):
2365
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2369
def initialize(self, a_bzrdir, name=None, repository=None):
2370
# 1) get the network name to use.
2371
if self._custom_format:
2372
network_name = self._custom_format.network_name()
2374
# Select the current bzrlib default and ask for that.
2375
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
2376
reference_format = reference_bzrdir_format.get_branch_format()
2377
self._custom_format = reference_format
2378
network_name = reference_format.network_name()
2379
# Being asked to create on a non RemoteBzrDir:
2380
if not isinstance(a_bzrdir, RemoteBzrDir):
2381
return self._vfs_initialize(a_bzrdir, name=name)
2382
medium = a_bzrdir._client._medium
2383
if medium._is_remote_before((1, 13)):
2384
return self._vfs_initialize(a_bzrdir, name=name)
2385
# Creating on a remote bzr dir.
2386
# 2) try direct creation via RPC
2387
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2388
if name is not None:
2389
# XXX JRV20100304: Support creating colocated branches
2390
raise errors.NoColocatedBranchSupport(self)
2391
verb = 'BzrDir.create_branch'
2393
response = a_bzrdir._call(verb, path, network_name)
2394
except errors.UnknownSmartMethod:
2395
# Fallback - use vfs methods
2396
medium._remember_remote_is_before((1, 13))
2397
return self._vfs_initialize(a_bzrdir, name=name)
2398
if response[0] != 'ok':
2399
raise errors.UnexpectedSmartServerResponse(response)
2400
# Turn the response into a RemoteRepository object.
2401
format = RemoteBranchFormat(network_name=response[1])
2402
repo_format = response_tuple_to_repo_format(response[3:])
2403
repo_path = response[2]
2404
if repository is not None:
2405
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
2406
url_diff = urlutils.relative_url(repository.user_url,
2409
raise AssertionError(
2410
'repository.user_url %r does not match URL from server '
2411
'response (%r + %r)'
2412
% (repository.user_url, a_bzrdir.user_url, repo_path))
2413
remote_repo = repository
2416
repo_bzrdir = a_bzrdir
2418
repo_bzrdir = RemoteBzrDir(
2419
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
2421
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2422
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2423
format=format, setup_stacking=False, name=name)
2424
# XXX: We know this is a new branch, so it must have revno 0, revid
2425
# NULL_REVISION. Creating the branch locked would make this be unable
2426
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2427
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2428
return remote_branch
2430
def make_tags(self, branch):
2432
return self._custom_format.make_tags(branch)
2434
def supports_tags(self):
2435
# Remote branches might support tags, but we won't know until we
2436
# access the real remote branch.
2438
return self._custom_format.supports_tags()
2440
def supports_stacking(self):
2442
return self._custom_format.supports_stacking()
2444
def supports_set_append_revisions_only(self):
2446
return self._custom_format.supports_set_append_revisions_only()
2448
def _use_default_local_heads_to_fetch(self):
2449
# If the branch format is a metadir format *and* its heads_to_fetch
2450
# implementation is not overridden vs the base class, we can use the
2451
# base class logic rather than use the heads_to_fetch RPC. This is
2452
# usually cheaper in terms of net round trips, as the last-revision and
2453
# tags info fetched is cached and would be fetched anyway.
2455
if isinstance(self._custom_format, branch.BranchFormatMetadir):
2456
branch_class = self._custom_format._branch_class()
2457
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
2458
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
2462
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2463
"""Branch stored on a server accessed by HPSS RPC.
2465
At the moment most operations are mapped down to simple file operations.
2468
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2469
_client=None, format=None, setup_stacking=True, name=None):
2470
"""Create a RemoteBranch instance.
2472
:param real_branch: An optional local implementation of the branch
2473
format, usually accessing the data via the VFS.
2474
:param _client: Private parameter for testing.
2475
:param format: A RemoteBranchFormat object, None to create one
2476
automatically. If supplied it should have a network_name already
2478
:param setup_stacking: If True make an RPC call to determine the
2479
stacked (or not) status of the branch. If False assume the branch
2481
:param name: Colocated branch name
2483
# We intentionally don't call the parent class's __init__, because it
2484
# will try to assign to self.tags, which is a property in this subclass.
2485
# And the parent's __init__ doesn't do much anyway.
2486
self.bzrdir = remote_bzrdir
2487
if _client is not None:
2488
self._client = _client
2490
self._client = remote_bzrdir._client
2491
self.repository = remote_repository
2492
if real_branch is not None:
2493
self._real_branch = real_branch
2494
# Give the remote repository the matching real repo.
2495
real_repo = self._real_branch.repository
2496
if isinstance(real_repo, RemoteRepository):
2497
real_repo._ensure_real()
2498
real_repo = real_repo._real_repository
2499
self.repository._set_real_repository(real_repo)
2500
# Give the branch the remote repository to let fast-pathing happen.
2501
self._real_branch.repository = self.repository
2503
self._real_branch = None
2504
# Fill out expected attributes of branch for bzrlib API users.
2505
self._clear_cached_state()
2506
# TODO: deprecate self.base in favor of user_url
2507
self.base = self.bzrdir.user_url
2509
self._control_files = None
2510
self._lock_mode = None
2511
self._lock_token = None
2512
self._repo_lock_token = None
2513
self._lock_count = 0
2514
self._leave_lock = False
2515
# Setup a format: note that we cannot call _ensure_real until all the
2516
# attributes above are set: This code cannot be moved higher up in this
2519
self._format = RemoteBranchFormat()
2520
if real_branch is not None:
2521
self._format._network_name = \
2522
self._real_branch._format.network_name()
2524
self._format = format
2525
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2526
# branch.open_branch method.
2527
self._real_ignore_fallbacks = not setup_stacking
2528
if not self._format._network_name:
2529
# Did not get from open_branchV2 - old server.
2531
self._format._network_name = \
2532
self._real_branch._format.network_name()
2533
self.tags = self._format.make_tags(self)
2534
# The base class init is not called, so we duplicate this:
2535
hooks = branch.Branch.hooks['open']
2538
self._is_stacked = False
2540
self._setup_stacking()
2542
def _setup_stacking(self):
2543
# configure stacking into the remote repository, by reading it from
2546
fallback_url = self.get_stacked_on_url()
2547
except (errors.NotStacked, errors.UnstackableBranchFormat,
2548
errors.UnstackableRepositoryFormat), e:
2550
self._is_stacked = True
2551
self._activate_fallback_location(fallback_url)
2553
def _get_config(self):
2554
return RemoteBranchConfig(self)
2556
def _get_real_transport(self):
2557
# if we try vfs access, return the real branch's vfs transport
2559
return self._real_branch._transport
2561
_transport = property(_get_real_transport)
2564
return "%s(%s)" % (self.__class__.__name__, self.base)
2568
def _ensure_real(self):
2569
"""Ensure that there is a _real_branch set.
2571
Used before calls to self._real_branch.
2573
if self._real_branch is None:
2574
if not vfs.vfs_enabled():
2575
raise AssertionError('smart server vfs must be enabled '
2576
'to use vfs implementation')
2577
self.bzrdir._ensure_real()
2578
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2579
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2580
if self.repository._real_repository is None:
2581
# Give the remote repository the matching real repo.
2582
real_repo = self._real_branch.repository
2583
if isinstance(real_repo, RemoteRepository):
2584
real_repo._ensure_real()
2585
real_repo = real_repo._real_repository
2586
self.repository._set_real_repository(real_repo)
2587
# Give the real branch the remote repository to let fast-pathing
2589
self._real_branch.repository = self.repository
2590
if self._lock_mode == 'r':
2591
self._real_branch.lock_read()
2592
elif self._lock_mode == 'w':
2593
self._real_branch.lock_write(token=self._lock_token)
2595
def _translate_error(self, err, **context):
2596
self.repository._translate_error(err, branch=self, **context)
2598
def _clear_cached_state(self):
2599
super(RemoteBranch, self)._clear_cached_state()
2600
if self._real_branch is not None:
2601
self._real_branch._clear_cached_state()
2603
def _clear_cached_state_of_remote_branch_only(self):
2604
"""Like _clear_cached_state, but doesn't clear the cache of
2607
This is useful when falling back to calling a method of
2608
self._real_branch that changes state. In that case the underlying
2609
branch changes, so we need to invalidate this RemoteBranch's cache of
2610
it. However, there's no need to invalidate the _real_branch's cache
2611
too, in fact doing so might harm performance.
2613
super(RemoteBranch, self)._clear_cached_state()
2616
def control_files(self):
2617
# Defer actually creating RemoteBranchLockableFiles until its needed,
2618
# because it triggers an _ensure_real that we otherwise might not need.
2619
if self._control_files is None:
2620
self._control_files = RemoteBranchLockableFiles(
2621
self.bzrdir, self._client)
2622
return self._control_files
2624
def _get_checkout_format(self):
2626
return self._real_branch._get_checkout_format()
2628
def get_physical_lock_status(self):
2629
"""See Branch.get_physical_lock_status()."""
2630
# should be an API call to the server, as branches must be lockable.
2632
return self._real_branch.get_physical_lock_status()
2634
def get_stacked_on_url(self):
2635
"""Get the URL this branch is stacked against.
2637
:raises NotStacked: If the branch is not stacked.
2638
:raises UnstackableBranchFormat: If the branch does not support
2640
:raises UnstackableRepositoryFormat: If the repository does not support
2644
# there may not be a repository yet, so we can't use
2645
# self._translate_error, so we can't use self._call either.
2646
response = self._client.call('Branch.get_stacked_on_url',
2647
self._remote_path())
2648
except errors.ErrorFromSmartServer, err:
2649
# there may not be a repository yet, so we can't call through
2650
# its _translate_error
2651
_translate_error(err, branch=self)
2652
except errors.UnknownSmartMethod, err:
2654
return self._real_branch.get_stacked_on_url()
2655
if response[0] != 'ok':
2656
raise errors.UnexpectedSmartServerResponse(response)
2659
def set_stacked_on_url(self, url):
2660
branch.Branch.set_stacked_on_url(self, url)
2662
self._is_stacked = False
2664
self._is_stacked = True
2666
def _vfs_get_tags_bytes(self):
2668
return self._real_branch._get_tags_bytes()
2671
def _get_tags_bytes(self):
2672
if self._tags_bytes is None:
2673
self._tags_bytes = self._get_tags_bytes_via_hpss()
2674
return self._tags_bytes
2676
def _get_tags_bytes_via_hpss(self):
2677
medium = self._client._medium
2678
if medium._is_remote_before((1, 13)):
2679
return self._vfs_get_tags_bytes()
2681
response = self._call('Branch.get_tags_bytes', self._remote_path())
2682
except errors.UnknownSmartMethod:
2683
medium._remember_remote_is_before((1, 13))
2684
return self._vfs_get_tags_bytes()
2687
def _vfs_set_tags_bytes(self, bytes):
2689
return self._real_branch._set_tags_bytes(bytes)
2691
def _set_tags_bytes(self, bytes):
2692
if self.is_locked():
2693
self._tags_bytes = bytes
2694
medium = self._client._medium
2695
if medium._is_remote_before((1, 18)):
2696
self._vfs_set_tags_bytes(bytes)
2700
self._remote_path(), self._lock_token, self._repo_lock_token)
2701
response = self._call_with_body_bytes(
2702
'Branch.set_tags_bytes', args, bytes)
2703
except errors.UnknownSmartMethod:
2704
medium._remember_remote_is_before((1, 18))
2705
self._vfs_set_tags_bytes(bytes)
2707
def lock_read(self):
2708
"""Lock the branch for read operations.
2710
:return: A bzrlib.lock.LogicalLockResult.
2712
self.repository.lock_read()
2713
if not self._lock_mode:
2714
self._note_lock('r')
2715
self._lock_mode = 'r'
2716
self._lock_count = 1
2717
if self._real_branch is not None:
2718
self._real_branch.lock_read()
2720
self._lock_count += 1
2721
return lock.LogicalLockResult(self.unlock)
2723
def _remote_lock_write(self, token):
2725
branch_token = repo_token = ''
2727
branch_token = token
2728
repo_token = self.repository.lock_write().repository_token
2729
self.repository.unlock()
2730
err_context = {'token': token}
2732
response = self._call(
2733
'Branch.lock_write', self._remote_path(), branch_token,
2734
repo_token or '', **err_context)
2735
except errors.LockContention, e:
2736
# The LockContention from the server doesn't have any
2737
# information about the lock_url. We re-raise LockContention
2738
# with valid lock_url.
2739
raise errors.LockContention('(remote lock)',
2740
self.repository.base.split('.bzr/')[0])
2741
if response[0] != 'ok':
2742
raise errors.UnexpectedSmartServerResponse(response)
2743
ok, branch_token, repo_token = response
2744
return branch_token, repo_token
2746
def lock_write(self, token=None):
2747
if not self._lock_mode:
2748
self._note_lock('w')
2749
# Lock the branch and repo in one remote call.
2750
remote_tokens = self._remote_lock_write(token)
2751
self._lock_token, self._repo_lock_token = remote_tokens
2752
if not self._lock_token:
2753
raise SmartProtocolError('Remote server did not return a token!')
2754
# Tell the self.repository object that it is locked.
2755
self.repository.lock_write(
2756
self._repo_lock_token, _skip_rpc=True)
2758
if self._real_branch is not None:
2759
self._real_branch.lock_write(token=self._lock_token)
2760
if token is not None:
2761
self._leave_lock = True
2763
self._leave_lock = False
2764
self._lock_mode = 'w'
2765
self._lock_count = 1
2766
elif self._lock_mode == 'r':
2767
raise errors.ReadOnlyError(self)
2769
if token is not None:
2770
# A token was given to lock_write, and we're relocking, so
2771
# check that the given token actually matches the one we
2773
if token != self._lock_token:
2774
raise errors.TokenMismatch(token, self._lock_token)
2775
self._lock_count += 1
2776
# Re-lock the repository too.
2777
self.repository.lock_write(self._repo_lock_token)
2778
return BranchWriteLockResult(self.unlock, self._lock_token or None)
2780
def _unlock(self, branch_token, repo_token):
2781
err_context = {'token': str((branch_token, repo_token))}
2782
response = self._call(
2783
'Branch.unlock', self._remote_path(), branch_token,
2784
repo_token or '', **err_context)
2785
if response == ('ok',):
2787
raise errors.UnexpectedSmartServerResponse(response)
2789
@only_raises(errors.LockNotHeld, errors.LockBroken)
2792
self._lock_count -= 1
2793
if not self._lock_count:
2794
self._clear_cached_state()
2795
mode = self._lock_mode
2796
self._lock_mode = None
2797
if self._real_branch is not None:
2798
if (not self._leave_lock and mode == 'w' and
2799
self._repo_lock_token):
2800
# If this RemoteBranch will remove the physical lock
2801
# for the repository, make sure the _real_branch
2802
# doesn't do it first. (Because the _real_branch's
2803
# repository is set to be the RemoteRepository.)
2804
self._real_branch.repository.leave_lock_in_place()
2805
self._real_branch.unlock()
2807
# Only write-locked branched need to make a remote method
2808
# call to perform the unlock.
2810
if not self._lock_token:
2811
raise AssertionError('Locked, but no token!')
2812
branch_token = self._lock_token
2813
repo_token = self._repo_lock_token
2814
self._lock_token = None
2815
self._repo_lock_token = None
2816
if not self._leave_lock:
2817
self._unlock(branch_token, repo_token)
2819
self.repository.unlock()
2821
def break_lock(self):
2823
return self._real_branch.break_lock()
2825
def leave_lock_in_place(self):
2826
if not self._lock_token:
2827
raise NotImplementedError(self.leave_lock_in_place)
2828
self._leave_lock = True
2830
def dont_leave_lock_in_place(self):
2831
if not self._lock_token:
2832
raise NotImplementedError(self.dont_leave_lock_in_place)
2833
self._leave_lock = False
2836
def get_rev_id(self, revno, history=None):
2838
return _mod_revision.NULL_REVISION
2839
last_revision_info = self.last_revision_info()
2840
ok, result = self.repository.get_rev_id_for_revno(
2841
revno, last_revision_info)
2844
missing_parent = result[1]
2845
# Either the revision named by the server is missing, or its parent
2846
# is. Call get_parent_map to determine which, so that we report a
2848
parent_map = self.repository.get_parent_map([missing_parent])
2849
if missing_parent in parent_map:
2850
missing_parent = parent_map[missing_parent]
2851
raise errors.RevisionNotPresent(missing_parent, self.repository)
2853
def _last_revision_info(self):
2854
response = self._call('Branch.last_revision_info', self._remote_path())
2855
if response[0] != 'ok':
2856
raise SmartProtocolError('unexpected response code %s' % (response,))
2857
revno = int(response[1])
2858
last_revision = response[2]
2859
return (revno, last_revision)
2861
def _gen_revision_history(self):
2862
"""See Branch._gen_revision_history()."""
2863
if self._is_stacked:
2865
return self._real_branch._gen_revision_history()
2866
response_tuple, response_handler = self._call_expecting_body(
2867
'Branch.revision_history', self._remote_path())
2868
if response_tuple[0] != 'ok':
2869
raise errors.UnexpectedSmartServerResponse(response_tuple)
2870
result = response_handler.read_body_bytes().split('\x00')
2875
def _remote_path(self):
2876
return self.bzrdir._path_for_remote_call(self._client)
2878
def _set_last_revision_descendant(self, revision_id, other_branch,
2879
allow_diverged=False, allow_overwrite_descendant=False):
2880
# This performs additional work to meet the hook contract; while its
2881
# undesirable, we have to synthesise the revno to call the hook, and
2882
# not calling the hook is worse as it means changes can't be prevented.
2883
# Having calculated this though, we can't just call into
2884
# set_last_revision_info as a simple call, because there is a set_rh
2885
# hook that some folk may still be using.
2886
old_revno, old_revid = self.last_revision_info()
2887
history = self._lefthand_history(revision_id)
2888
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2889
err_context = {'other_branch': other_branch}
2890
response = self._call('Branch.set_last_revision_ex',
2891
self._remote_path(), self._lock_token, self._repo_lock_token,
2892
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2894
self._clear_cached_state()
2895
if len(response) != 3 and response[0] != 'ok':
2896
raise errors.UnexpectedSmartServerResponse(response)
2897
new_revno, new_revision_id = response[1:]
2898
self._last_revision_info_cache = new_revno, new_revision_id
2899
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2900
if self._real_branch is not None:
2901
cache = new_revno, new_revision_id
2902
self._real_branch._last_revision_info_cache = cache
2904
def _set_last_revision(self, revision_id):
2905
old_revno, old_revid = self.last_revision_info()
2906
# This performs additional work to meet the hook contract; while its
2907
# undesirable, we have to synthesise the revno to call the hook, and
2908
# not calling the hook is worse as it means changes can't be prevented.
2909
# Having calculated this though, we can't just call into
2910
# set_last_revision_info as a simple call, because there is a set_rh
2911
# hook that some folk may still be using.
2912
history = self._lefthand_history(revision_id)
2913
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2914
self._clear_cached_state()
2915
response = self._call('Branch.set_last_revision',
2916
self._remote_path(), self._lock_token, self._repo_lock_token,
2918
if response != ('ok',):
2919
raise errors.UnexpectedSmartServerResponse(response)
2920
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2922
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2924
def set_revision_history(self, rev_history):
2925
"""See Branch.set_revision_history."""
2926
self._set_revision_history(rev_history)
2929
def _set_revision_history(self, rev_history):
2930
# Send just the tip revision of the history; the server will generate
2931
# the full history from that. If the revision doesn't exist in this
2932
# branch, NoSuchRevision will be raised.
2933
if rev_history == []:
2936
rev_id = rev_history[-1]
2937
self._set_last_revision(rev_id)
2938
for hook in branch.Branch.hooks['set_rh']:
2939
hook(self, rev_history)
2940
self._cache_revision_history(rev_history)
2942
def _get_parent_location(self):
2943
medium = self._client._medium
2944
if medium._is_remote_before((1, 13)):
2945
return self._vfs_get_parent_location()
2947
response = self._call('Branch.get_parent', self._remote_path())
2948
except errors.UnknownSmartMethod:
2949
medium._remember_remote_is_before((1, 13))
2950
return self._vfs_get_parent_location()
2951
if len(response) != 1:
2952
raise errors.UnexpectedSmartServerResponse(response)
2953
parent_location = response[0]
2954
if parent_location == '':
2956
return parent_location
2958
def _vfs_get_parent_location(self):
2960
return self._real_branch._get_parent_location()
2962
def _set_parent_location(self, url):
2963
medium = self._client._medium
2964
if medium._is_remote_before((1, 15)):
2965
return self._vfs_set_parent_location(url)
2967
call_url = url or ''
2968
if type(call_url) is not str:
2969
raise AssertionError('url must be a str or None (%s)' % url)
2970
response = self._call('Branch.set_parent_location',
2971
self._remote_path(), self._lock_token, self._repo_lock_token,
2973
except errors.UnknownSmartMethod:
2974
medium._remember_remote_is_before((1, 15))
2975
return self._vfs_set_parent_location(url)
2977
raise errors.UnexpectedSmartServerResponse(response)
2979
def _vfs_set_parent_location(self, url):
2981
return self._real_branch._set_parent_location(url)
2984
def pull(self, source, overwrite=False, stop_revision=None,
2986
self._clear_cached_state_of_remote_branch_only()
2988
return self._real_branch.pull(
2989
source, overwrite=overwrite, stop_revision=stop_revision,
2990
_override_hook_target=self, **kwargs)
2993
def push(self, target, overwrite=False, stop_revision=None):
2995
return self._real_branch.push(
2996
target, overwrite=overwrite, stop_revision=stop_revision,
2997
_override_hook_source_branch=self)
2999
def is_locked(self):
3000
return self._lock_count >= 1
3003
def revision_id_to_revno(self, revision_id):
3005
return self._real_branch.revision_id_to_revno(revision_id)
3008
def set_last_revision_info(self, revno, revision_id):
3009
# XXX: These should be returned by the set_last_revision_info verb
3010
old_revno, old_revid = self.last_revision_info()
3011
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3012
if not revision_id or not isinstance(revision_id, basestring):
3013
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3015
response = self._call('Branch.set_last_revision_info',
3016
self._remote_path(), self._lock_token, self._repo_lock_token,
3017
str(revno), revision_id)
3018
except errors.UnknownSmartMethod:
3020
self._clear_cached_state_of_remote_branch_only()
3021
self._real_branch.set_last_revision_info(revno, revision_id)
3022
self._last_revision_info_cache = revno, revision_id
3024
if response == ('ok',):
3025
self._clear_cached_state()
3026
self._last_revision_info_cache = revno, revision_id
3027
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3028
# Update the _real_branch's cache too.
3029
if self._real_branch is not None:
3030
cache = self._last_revision_info_cache
3031
self._real_branch._last_revision_info_cache = cache
3033
raise errors.UnexpectedSmartServerResponse(response)
3036
def generate_revision_history(self, revision_id, last_rev=None,
3038
medium = self._client._medium
3039
if not medium._is_remote_before((1, 6)):
3040
# Use a smart method for 1.6 and above servers
3042
self._set_last_revision_descendant(revision_id, other_branch,
3043
allow_diverged=True, allow_overwrite_descendant=True)
3045
except errors.UnknownSmartMethod:
3046
medium._remember_remote_is_before((1, 6))
3047
self._clear_cached_state_of_remote_branch_only()
3048
self._set_revision_history(self._lefthand_history(revision_id,
3049
last_rev=last_rev,other_branch=other_branch))
3051
def set_push_location(self, location):
3053
return self._real_branch.set_push_location(location)
3055
def heads_to_fetch(self):
3056
if self._format._use_default_local_heads_to_fetch():
3057
# We recognise this format, and its heads-to-fetch implementation
3058
# is the default one (tip + tags). In this case it's cheaper to
3059
# just use the default implementation rather than a special RPC as
3060
# the tip and tags data is cached.
3061
return branch.Branch.heads_to_fetch(self)
3062
medium = self._client._medium
3063
if medium._is_remote_before((2, 4)):
3064
return self._vfs_heads_to_fetch()
3066
return self._rpc_heads_to_fetch()
3067
except errors.UnknownSmartMethod:
3068
medium._remember_remote_is_before((2, 4))
3069
return self._vfs_heads_to_fetch()
3071
def _rpc_heads_to_fetch(self):
3072
response = self._call('Branch.heads_to_fetch', self._remote_path())
3073
if len(response) != 2:
3074
raise errors.UnexpectedSmartServerResponse(response)
3075
must_fetch, if_present_fetch = response
3076
return set(must_fetch), set(if_present_fetch)
3078
def _vfs_heads_to_fetch(self):
3080
return self._real_branch.heads_to_fetch()
3083
class RemoteConfig(object):
3084
"""A Config that reads and writes from smart verbs.
3086
It is a low-level object that considers config data to be name/value pairs
3087
that may be associated with a section. Assigning meaning to the these
3088
values is done at higher levels like bzrlib.config.TreeConfig.
3091
def get_option(self, name, section=None, default=None):
3092
"""Return the value associated with a named option.
3094
:param name: The name of the value
3095
:param section: The section the option is in (if any)
3096
:param default: The value to return if the value is not set
3097
:return: The value or default value
3100
configobj = self._get_configobj()
3102
section_obj = configobj
3105
section_obj = configobj[section]
3108
return section_obj.get(name, default)
3109
except errors.UnknownSmartMethod:
3110
return self._vfs_get_option(name, section, default)
3112
def _response_to_configobj(self, response):
3113
if len(response[0]) and response[0][0] != 'ok':
3114
raise errors.UnexpectedSmartServerResponse(response)
3115
lines = response[1].read_body_bytes().splitlines()
3116
return config.ConfigObj(lines, encoding='utf-8')
3119
class RemoteBranchConfig(RemoteConfig):
3120
"""A RemoteConfig for Branches."""
3122
def __init__(self, branch):
3123
self._branch = branch
3125
def _get_configobj(self):
3126
path = self._branch._remote_path()
3127
response = self._branch._client.call_expecting_body(
3128
'Branch.get_config_file', path)
3129
return self._response_to_configobj(response)
3131
def set_option(self, value, name, section=None):
3132
"""Set the value associated with a named option.
3134
:param value: The value to set
3135
:param name: The name of the value to set
3136
:param section: The section the option is in (if any)
3138
medium = self._branch._client._medium
3139
if medium._is_remote_before((1, 14)):
3140
return self._vfs_set_option(value, name, section)
3141
if isinstance(value, dict):
3142
if medium._is_remote_before((2, 2)):
3143
return self._vfs_set_option(value, name, section)
3144
return self._set_config_option_dict(value, name, section)
3146
return self._set_config_option(value, name, section)
3148
def _set_config_option(self, value, name, section):
3150
path = self._branch._remote_path()
3151
response = self._branch._client.call('Branch.set_config_option',
3152
path, self._branch._lock_token, self._branch._repo_lock_token,
3153
value.encode('utf8'), name, section or '')
3154
except errors.UnknownSmartMethod:
3155
medium = self._branch._client._medium
3156
medium._remember_remote_is_before((1, 14))
3157
return self._vfs_set_option(value, name, section)
3159
raise errors.UnexpectedSmartServerResponse(response)
3161
def _serialize_option_dict(self, option_dict):
3163
for key, value in option_dict.items():
3164
if isinstance(key, unicode):
3165
key = key.encode('utf8')
3166
if isinstance(value, unicode):
3167
value = value.encode('utf8')
3168
utf8_dict[key] = value
3169
return bencode.bencode(utf8_dict)
3171
def _set_config_option_dict(self, value, name, section):
3173
path = self._branch._remote_path()
3174
serialised_dict = self._serialize_option_dict(value)
3175
response = self._branch._client.call(
3176
'Branch.set_config_option_dict',
3177
path, self._branch._lock_token, self._branch._repo_lock_token,
3178
serialised_dict, name, section or '')
3179
except errors.UnknownSmartMethod:
3180
medium = self._branch._client._medium
3181
medium._remember_remote_is_before((2, 2))
3182
return self._vfs_set_option(value, name, section)
3184
raise errors.UnexpectedSmartServerResponse(response)
3186
def _real_object(self):
3187
self._branch._ensure_real()
3188
return self._branch._real_branch
3190
def _vfs_set_option(self, value, name, section=None):
3191
return self._real_object()._get_config().set_option(
3192
value, name, section)
3195
class RemoteBzrDirConfig(RemoteConfig):
3196
"""A RemoteConfig for BzrDirs."""
3198
def __init__(self, bzrdir):
3199
self._bzrdir = bzrdir
3201
def _get_configobj(self):
3202
medium = self._bzrdir._client._medium
3203
verb = 'BzrDir.get_config_file'
3204
if medium._is_remote_before((1, 15)):
3205
raise errors.UnknownSmartMethod(verb)
3206
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
3207
response = self._bzrdir._call_expecting_body(
3209
return self._response_to_configobj(response)
3211
def _vfs_get_option(self, name, section, default):
3212
return self._real_object()._get_config().get_option(
3213
name, section, default)
3215
def set_option(self, value, name, section=None):
3216
"""Set the value associated with a named option.
3218
:param value: The value to set
3219
:param name: The name of the value to set
3220
:param section: The section the option is in (if any)
3222
return self._real_object()._get_config().set_option(
3223
value, name, section)
3225
def _real_object(self):
3226
self._bzrdir._ensure_real()
3227
return self._bzrdir._real_bzrdir
3231
def _extract_tar(tar, to_dir):
3232
"""Extract all the contents of a tarfile object.
3234
A replacement for extractall, which is not present in python2.4
3237
tar.extract(tarinfo, to_dir)
3240
def _translate_error(err, **context):
3241
"""Translate an ErrorFromSmartServer into a more useful error.
3243
Possible context keys:
3251
If the error from the server doesn't match a known pattern, then
3252
UnknownErrorFromSmartServer is raised.
3256
return context[name]
3257
except KeyError, key_err:
3258
mutter('Missing key %r in context %r', key_err.args[0], context)
3261
"""Get the path from the context if present, otherwise use first error
3265
return context['path']
3266
except KeyError, key_err:
3268
return err.error_args[0]
3269
except IndexError, idx_err:
3271
'Missing key %r in context %r', key_err.args[0], context)
3274
if err.error_verb == 'NoSuchRevision':
3275
raise NoSuchRevision(find('branch'), err.error_args[0])
3276
elif err.error_verb == 'nosuchrevision':
3277
raise NoSuchRevision(find('repository'), err.error_args[0])
3278
elif err.error_verb == 'nobranch':
3279
if len(err.error_args) >= 1:
3280
extra = err.error_args[0]
3283
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
3285
elif err.error_verb == 'norepository':
3286
raise errors.NoRepositoryPresent(find('bzrdir'))
3287
elif err.error_verb == 'UnlockableTransport':
3288
raise errors.UnlockableTransport(find('bzrdir').root_transport)
3289
elif err.error_verb == 'TokenMismatch':
3290
raise errors.TokenMismatch(find('token'), '(remote token)')
3291
elif err.error_verb == 'Diverged':
3292
raise errors.DivergedBranches(find('branch'), find('other_branch'))
3293
elif err.error_verb == 'NotStacked':
3294
raise errors.NotStacked(branch=find('branch'))
3295
elif err.error_verb == 'PermissionDenied':
3297
if len(err.error_args) >= 2:
3298
extra = err.error_args[1]
3301
raise errors.PermissionDenied(path, extra=extra)
3302
elif err.error_verb == 'ReadError':
3304
raise errors.ReadError(path)
3305
elif err.error_verb == 'NoSuchFile':
3307
raise errors.NoSuchFile(path)
3308
_translate_error_without_context(err)
3311
def _translate_error_without_context(err):
3312
"""Translate any ErrorFromSmartServer values that don't require context"""
3313
if err.error_verb == 'IncompatibleRepositories':
3314
raise errors.IncompatibleRepositories(err.error_args[0],
3315
err.error_args[1], err.error_args[2])
3316
elif err.error_verb == 'LockContention':
3317
raise errors.LockContention('(remote lock)')
3318
elif err.error_verb == 'LockFailed':
3319
raise errors.LockFailed(err.error_args[0], err.error_args[1])
3320
elif err.error_verb == 'TipChangeRejected':
3321
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
3322
elif err.error_verb == 'UnstackableBranchFormat':
3323
raise errors.UnstackableBranchFormat(*err.error_args)
3324
elif err.error_verb == 'UnstackableRepositoryFormat':
3325
raise errors.UnstackableRepositoryFormat(*err.error_args)
3326
elif err.error_verb == 'FileExists':
3327
raise errors.FileExists(err.error_args[0])
3328
elif err.error_verb == 'DirectoryNotEmpty':
3329
raise errors.DirectoryNotEmpty(err.error_args[0])
3330
elif err.error_verb == 'ShortReadvError':
3331
args = err.error_args
3332
raise errors.ShortReadvError(
3333
args[0], int(args[1]), int(args[2]), int(args[3]))
3334
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
3335
encoding = str(err.error_args[0]) # encoding must always be a string
3336
val = err.error_args[1]
3337
start = int(err.error_args[2])
3338
end = int(err.error_args[3])
3339
reason = str(err.error_args[4]) # reason must always be a string
3340
if val.startswith('u:'):
3341
val = val[2:].decode('utf-8')
3342
elif val.startswith('s:'):
3343
val = val[2:].decode('base64')
3344
if err.error_verb == 'UnicodeDecodeError':
3345
raise UnicodeDecodeError(encoding, val, start, end, reason)
3346
elif err.error_verb == 'UnicodeEncodeError':
3347
raise UnicodeEncodeError(encoding, val, start, end, reason)
3348
elif err.error_verb == 'ReadOnlyError':
3349
raise errors.TransportNotPossible('readonly transport')
3350
elif err.error_verb == 'MemoryError':
3351
raise errors.BzrError("remote server out of memory\n"
3352
"Retry non-remotely, or contact the server admin for details.")
3353
raise errors.UnknownErrorFromSmartServer(err)