1
# Copyright (C) 2006-2010 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
31
revision as _mod_revision,
34
from bzrlib.branch import BranchReferenceFormat
35
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
36
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
37
from bzrlib.errors import (
41
from bzrlib.lockable_files import LockableFiles
42
from bzrlib.smart import client, vfs, repository as smart_repo
43
from bzrlib.revision import ensure_null, NULL_REVISION
44
from bzrlib.trace import mutter, note, warning
47
class _RpcHelper(object):
48
"""Mixin class that helps with issuing RPCs."""
50
def _call(self, method, *args, **err_context):
52
return self._client.call(method, *args)
53
except errors.ErrorFromSmartServer, err:
54
self._translate_error(err, **err_context)
56
def _call_expecting_body(self, method, *args, **err_context):
58
return self._client.call_expecting_body(method, *args)
59
except errors.ErrorFromSmartServer, err:
60
self._translate_error(err, **err_context)
62
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
64
return self._client.call_with_body_bytes(method, args, body_bytes)
65
except errors.ErrorFromSmartServer, err:
66
self._translate_error(err, **err_context)
68
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
71
return self._client.call_with_body_bytes_expecting_body(
72
method, args, body_bytes)
73
except errors.ErrorFromSmartServer, err:
74
self._translate_error(err, **err_context)
77
def response_tuple_to_repo_format(response):
78
"""Convert a response tuple describing a repository format to a format."""
79
format = RemoteRepositoryFormat()
80
format._rich_root_data = (response[0] == 'yes')
81
format._supports_tree_reference = (response[1] == 'yes')
82
format._supports_external_lookups = (response[2] == 'yes')
83
format._network_name = response[3]
87
# Note: RemoteBzrDirFormat is in bzrdir.py
89
class RemoteBzrDir(BzrDir, _RpcHelper):
90
"""Control directory on a remote server, accessed via bzr:// or similar."""
92
def __init__(self, transport, format, _client=None, _force_probe=False):
93
"""Construct a RemoteBzrDir.
95
:param _client: Private parameter for testing. Disables probing and the
98
BzrDir.__init__(self, transport, format)
99
# this object holds a delegated bzrdir that uses file-level operations
100
# to talk to the other side
101
self._real_bzrdir = None
102
self._has_working_tree = None
103
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
104
# create_branch for details.
105
self._next_open_branch_result = None
108
medium = transport.get_smart_medium()
109
self._client = client._SmartClient(medium)
111
self._client = _client
118
return '%s(%r)' % (self.__class__.__name__, self._client)
120
def _probe_bzrdir(self):
121
medium = self._client._medium
122
path = self._path_for_remote_call(self._client)
123
if medium._is_remote_before((2, 1)):
127
self._rpc_open_2_1(path)
129
except errors.UnknownSmartMethod:
130
medium._remember_remote_is_before((2, 1))
133
def _rpc_open_2_1(self, path):
134
response = self._call('BzrDir.open_2.1', path)
135
if response == ('no',):
136
raise errors.NotBranchError(path=self.root_transport.base)
137
elif response[0] == 'yes':
138
if response[1] == 'yes':
139
self._has_working_tree = True
140
elif response[1] == 'no':
141
self._has_working_tree = False
143
raise errors.UnexpectedSmartServerResponse(response)
145
raise errors.UnexpectedSmartServerResponse(response)
147
def _rpc_open(self, path):
148
response = self._call('BzrDir.open', path)
149
if response not in [('yes',), ('no',)]:
150
raise errors.UnexpectedSmartServerResponse(response)
151
if response == ('no',):
152
raise errors.NotBranchError(path=self.root_transport.base)
154
def _ensure_real(self):
155
"""Ensure that there is a _real_bzrdir set.
157
Used before calls to self._real_bzrdir.
159
if not self._real_bzrdir:
160
if 'hpssvfs' in debug.debug_flags:
162
warning('VFS BzrDir access triggered\n%s',
163
''.join(traceback.format_stack()))
164
self._real_bzrdir = BzrDir.open_from_transport(
165
self.root_transport, _server_formats=False)
166
self._format._network_name = \
167
self._real_bzrdir._format.network_name()
169
def _translate_error(self, err, **context):
170
_translate_error(err, bzrdir=self, **context)
172
def break_lock(self):
173
# Prevent aliasing problems in the next_open_branch_result cache.
174
# See create_branch for rationale.
175
self._next_open_branch_result = None
176
return BzrDir.break_lock(self)
178
def _vfs_cloning_metadir(self, require_stacking=False):
180
return self._real_bzrdir.cloning_metadir(
181
require_stacking=require_stacking)
183
def cloning_metadir(self, require_stacking=False):
184
medium = self._client._medium
185
if medium._is_remote_before((1, 13)):
186
return self._vfs_cloning_metadir(require_stacking=require_stacking)
187
verb = 'BzrDir.cloning_metadir'
192
path = self._path_for_remote_call(self._client)
194
response = self._call(verb, path, stacking)
195
except errors.UnknownSmartMethod:
196
medium._remember_remote_is_before((1, 13))
197
return self._vfs_cloning_metadir(require_stacking=require_stacking)
198
except errors.UnknownErrorFromSmartServer, err:
199
if err.error_tuple != ('BranchReference',):
201
# We need to resolve the branch reference to determine the
202
# cloning_metadir. This causes unnecessary RPCs to open the
203
# referenced branch (and bzrdir, etc) but only when the caller
204
# didn't already resolve the branch reference.
205
referenced_branch = self.open_branch()
206
return referenced_branch.bzrdir.cloning_metadir()
207
if len(response) != 3:
208
raise errors.UnexpectedSmartServerResponse(response)
209
control_name, repo_name, branch_info = response
210
if len(branch_info) != 2:
211
raise errors.UnexpectedSmartServerResponse(response)
212
branch_ref, branch_name = branch_info
213
format = bzrdir.network_format_registry.get(control_name)
215
format.repository_format = repository.network_format_registry.get(
217
if branch_ref == 'ref':
218
# XXX: we need possible_transports here to avoid reopening the
219
# connection to the referenced location
220
ref_bzrdir = BzrDir.open(branch_name)
221
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
222
format.set_branch_format(branch_format)
223
elif branch_ref == 'branch':
225
format.set_branch_format(
226
branch.network_format_registry.get(branch_name))
228
raise errors.UnexpectedSmartServerResponse(response)
231
def create_repository(self, shared=False):
232
# as per meta1 formats - just delegate to the format object which may
234
result = self._format.repository_format.initialize(self, shared)
235
if not isinstance(result, RemoteRepository):
236
return self.open_repository()
240
def destroy_repository(self):
241
"""See BzrDir.destroy_repository"""
243
self._real_bzrdir.destroy_repository()
245
def create_branch(self, name=None):
246
# as per meta1 formats - just delegate to the format object which may
248
real_branch = self._format.get_branch_format().initialize(self,
250
if not isinstance(real_branch, RemoteBranch):
251
result = RemoteBranch(self, self.find_repository(), real_branch,
255
# BzrDir.clone_on_transport() uses the result of create_branch but does
256
# not return it to its callers; we save approximately 8% of our round
257
# trips by handing the branch we created back to the first caller to
258
# open_branch rather than probing anew. Long term we need a API in
259
# bzrdir that doesn't discard result objects (like result_branch).
261
self._next_open_branch_result = result
264
def destroy_branch(self, name=None):
265
"""See BzrDir.destroy_branch"""
267
self._real_bzrdir.destroy_branch(name=name)
268
self._next_open_branch_result = None
270
def create_workingtree(self, revision_id=None, from_branch=None):
271
raise errors.NotLocalUrl(self.transport.base)
273
def find_branch_format(self):
274
"""Find the branch 'format' for this bzrdir.
276
This might be a synthetic object for e.g. RemoteBranch and SVN.
278
b = self.open_branch()
281
def get_branch_reference(self):
282
"""See BzrDir.get_branch_reference()."""
283
response = self._get_branch_reference()
284
if response[0] == 'ref':
289
def _get_branch_reference(self):
290
path = self._path_for_remote_call(self._client)
291
medium = self._client._medium
293
('BzrDir.open_branchV3', (2, 1)),
294
('BzrDir.open_branchV2', (1, 13)),
295
('BzrDir.open_branch', None),
297
for verb, required_version in candidate_calls:
298
if required_version and medium._is_remote_before(required_version):
301
response = self._call(verb, path)
302
except errors.UnknownSmartMethod:
303
if required_version is None:
305
medium._remember_remote_is_before(required_version)
308
if verb == 'BzrDir.open_branch':
309
if response[0] != 'ok':
310
raise errors.UnexpectedSmartServerResponse(response)
311
if response[1] != '':
312
return ('ref', response[1])
314
return ('branch', '')
315
if response[0] not in ('ref', 'branch'):
316
raise errors.UnexpectedSmartServerResponse(response)
319
def _get_tree_branch(self):
320
"""See BzrDir._get_tree_branch()."""
321
return None, self.open_branch()
323
def open_branch(self, name=None, unsupported=False,
324
ignore_fallbacks=False):
326
raise NotImplementedError('unsupported flag support not implemented yet.')
327
if self._next_open_branch_result is not None:
328
# See create_branch for details.
329
result = self._next_open_branch_result
330
self._next_open_branch_result = None
332
response = self._get_branch_reference()
333
if response[0] == 'ref':
334
# a branch reference, use the existing BranchReference logic.
335
format = BranchReferenceFormat()
336
return format.open(self, name=name, _found=True,
337
location=response[1], ignore_fallbacks=ignore_fallbacks)
338
branch_format_name = response[1]
339
if not branch_format_name:
340
branch_format_name = None
341
format = RemoteBranchFormat(network_name=branch_format_name)
342
return RemoteBranch(self, self.find_repository(), format=format,
343
setup_stacking=not ignore_fallbacks, name=name)
345
def _open_repo_v1(self, path):
346
verb = 'BzrDir.find_repository'
347
response = self._call(verb, path)
348
if response[0] != 'ok':
349
raise errors.UnexpectedSmartServerResponse(response)
350
# servers that only support the v1 method don't support external
353
repo = self._real_bzrdir.open_repository()
354
response = response + ('no', repo._format.network_name())
355
return response, repo
357
def _open_repo_v2(self, path):
358
verb = 'BzrDir.find_repositoryV2'
359
response = self._call(verb, path)
360
if response[0] != 'ok':
361
raise errors.UnexpectedSmartServerResponse(response)
363
repo = self._real_bzrdir.open_repository()
364
response = response + (repo._format.network_name(),)
365
return response, repo
367
def _open_repo_v3(self, path):
368
verb = 'BzrDir.find_repositoryV3'
369
medium = self._client._medium
370
if medium._is_remote_before((1, 13)):
371
raise errors.UnknownSmartMethod(verb)
373
response = self._call(verb, path)
374
except errors.UnknownSmartMethod:
375
medium._remember_remote_is_before((1, 13))
377
if response[0] != 'ok':
378
raise errors.UnexpectedSmartServerResponse(response)
379
return response, None
381
def open_repository(self):
382
path = self._path_for_remote_call(self._client)
384
for probe in [self._open_repo_v3, self._open_repo_v2,
387
response, real_repo = probe(path)
389
except errors.UnknownSmartMethod:
392
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
393
if response[0] != 'ok':
394
raise errors.UnexpectedSmartServerResponse(response)
395
if len(response) != 6:
396
raise SmartProtocolError('incorrect response length %s' % (response,))
397
if response[1] == '':
398
# repo is at this dir.
399
format = response_tuple_to_repo_format(response[2:])
400
# Used to support creating a real format instance when needed.
401
format._creating_bzrdir = self
402
remote_repo = RemoteRepository(self, format)
403
format._creating_repo = remote_repo
404
if real_repo is not None:
405
remote_repo._set_real_repository(real_repo)
408
raise errors.NoRepositoryPresent(self)
410
def has_workingtree(self):
411
if self._has_working_tree is None:
413
self._has_working_tree = self._real_bzrdir.has_workingtree()
414
return self._has_working_tree
416
def open_workingtree(self, recommend_upgrade=True):
417
if self.has_workingtree():
418
raise errors.NotLocalUrl(self.root_transport)
420
raise errors.NoWorkingTree(self.root_transport.base)
422
def _path_for_remote_call(self, client):
423
"""Return the path to be used for this bzrdir in a remote call."""
424
return client.remote_path_from_transport(self.root_transport)
426
def get_branch_transport(self, branch_format, name=None):
428
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
430
def get_repository_transport(self, repository_format):
432
return self._real_bzrdir.get_repository_transport(repository_format)
434
def get_workingtree_transport(self, workingtree_format):
436
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
438
def can_convert_format(self):
439
"""Upgrading of remote bzrdirs is not supported yet."""
442
def needs_format_conversion(self, format=None):
443
"""Upgrading of remote bzrdirs is not supported yet."""
445
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
446
% 'needs_format_conversion(format=None)')
449
def clone(self, url, revision_id=None, force_new_repo=False,
450
preserve_stacking=False):
452
return self._real_bzrdir.clone(url, revision_id=revision_id,
453
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
455
def _get_config(self):
456
return RemoteBzrDirConfig(self)
459
class RemoteRepositoryFormat(repository.RepositoryFormat):
460
"""Format for repositories accessed over a _SmartClient.
462
Instances of this repository are represented by RemoteRepository
465
The RemoteRepositoryFormat is parameterized during construction
466
to reflect the capabilities of the real, remote format. Specifically
467
the attributes rich_root_data and supports_tree_reference are set
468
on a per instance basis, and are not set (and should not be) at
471
:ivar _custom_format: If set, a specific concrete repository format that
472
will be used when initializing a repository with this
473
RemoteRepositoryFormat.
474
:ivar _creating_repo: If set, the repository object that this
475
RemoteRepositoryFormat was created for: it can be called into
476
to obtain data like the network name.
479
_matchingbzrdir = RemoteBzrDirFormat()
482
repository.RepositoryFormat.__init__(self)
483
self._custom_format = None
484
self._network_name = None
485
self._creating_bzrdir = None
486
self._supports_chks = None
487
self._supports_external_lookups = None
488
self._supports_tree_reference = None
489
self._rich_root_data = None
492
return "%s(_network_name=%r)" % (self.__class__.__name__,
496
def fast_deltas(self):
498
return self._custom_format.fast_deltas
501
def rich_root_data(self):
502
if self._rich_root_data is None:
504
self._rich_root_data = self._custom_format.rich_root_data
505
return self._rich_root_data
508
def supports_chks(self):
509
if self._supports_chks is None:
511
self._supports_chks = self._custom_format.supports_chks
512
return self._supports_chks
515
def supports_external_lookups(self):
516
if self._supports_external_lookups is None:
518
self._supports_external_lookups = \
519
self._custom_format.supports_external_lookups
520
return self._supports_external_lookups
523
def supports_tree_reference(self):
524
if self._supports_tree_reference is None:
526
self._supports_tree_reference = \
527
self._custom_format.supports_tree_reference
528
return self._supports_tree_reference
530
def _vfs_initialize(self, a_bzrdir, shared):
531
"""Helper for common code in initialize."""
532
if self._custom_format:
533
# Custom format requested
534
result = self._custom_format.initialize(a_bzrdir, shared=shared)
535
elif self._creating_bzrdir is not None:
536
# Use the format that the repository we were created to back
538
prior_repo = self._creating_bzrdir.open_repository()
539
prior_repo._ensure_real()
540
result = prior_repo._real_repository._format.initialize(
541
a_bzrdir, shared=shared)
543
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
544
# support remote initialization.
545
# We delegate to a real object at this point (as RemoteBzrDir
546
# delegate to the repository format which would lead to infinite
547
# recursion if we just called a_bzrdir.create_repository.
548
a_bzrdir._ensure_real()
549
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
550
if not isinstance(result, RemoteRepository):
551
return self.open(a_bzrdir)
555
def initialize(self, a_bzrdir, shared=False):
556
# Being asked to create on a non RemoteBzrDir:
557
if not isinstance(a_bzrdir, RemoteBzrDir):
558
return self._vfs_initialize(a_bzrdir, shared)
559
medium = a_bzrdir._client._medium
560
if medium._is_remote_before((1, 13)):
561
return self._vfs_initialize(a_bzrdir, shared)
562
# Creating on a remote bzr dir.
563
# 1) get the network name to use.
564
if self._custom_format:
565
network_name = self._custom_format.network_name()
566
elif self._network_name:
567
network_name = self._network_name
569
# Select the current bzrlib default and ask for that.
570
reference_bzrdir_format = bzrdir.format_registry.get('default')()
571
reference_format = reference_bzrdir_format.repository_format
572
network_name = reference_format.network_name()
573
# 2) try direct creation via RPC
574
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
575
verb = 'BzrDir.create_repository'
581
response = a_bzrdir._call(verb, path, network_name, shared_str)
582
except errors.UnknownSmartMethod:
583
# Fallback - use vfs methods
584
medium._remember_remote_is_before((1, 13))
585
return self._vfs_initialize(a_bzrdir, shared)
587
# Turn the response into a RemoteRepository object.
588
format = response_tuple_to_repo_format(response[1:])
589
# Used to support creating a real format instance when needed.
590
format._creating_bzrdir = a_bzrdir
591
remote_repo = RemoteRepository(a_bzrdir, format)
592
format._creating_repo = remote_repo
595
def open(self, a_bzrdir):
596
if not isinstance(a_bzrdir, RemoteBzrDir):
597
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
598
return a_bzrdir.open_repository()
600
def _ensure_real(self):
601
if self._custom_format is None:
602
self._custom_format = repository.network_format_registry.get(
606
def _fetch_order(self):
608
return self._custom_format._fetch_order
611
def _fetch_uses_deltas(self):
613
return self._custom_format._fetch_uses_deltas
616
def _fetch_reconcile(self):
618
return self._custom_format._fetch_reconcile
620
def get_format_description(self):
622
return 'Remote: ' + self._custom_format.get_format_description()
624
def __eq__(self, other):
625
return self.__class__ is other.__class__
627
def network_name(self):
628
if self._network_name:
629
return self._network_name
630
self._creating_repo._ensure_real()
631
return self._creating_repo._real_repository._format.network_name()
634
def pack_compresses(self):
636
return self._custom_format.pack_compresses
639
def _serializer(self):
641
return self._custom_format._serializer
644
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
645
"""Repository accessed over rpc.
647
For the moment most operations are performed using local transport-backed
651
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
652
"""Create a RemoteRepository instance.
654
:param remote_bzrdir: The bzrdir hosting this repository.
655
:param format: The RemoteFormat object to use.
656
:param real_repository: If not None, a local implementation of the
657
repository logic for the repository, usually accessing the data
659
:param _client: Private testing parameter - override the smart client
660
to be used by the repository.
663
self._real_repository = real_repository
665
self._real_repository = None
666
self.bzrdir = remote_bzrdir
668
self._client = remote_bzrdir._client
670
self._client = _client
671
self._format = format
672
self._lock_mode = None
673
self._lock_token = None
675
self._leave_lock = False
676
# Cache of revision parents; misses are cached during read locks, and
677
# write locks when no _real_repository has been set.
678
self._unstacked_provider = graph.CachingParentsProvider(
679
get_parent_map=self._get_parent_map_rpc)
680
self._unstacked_provider.disable_cache()
682
# These depend on the actual remote format, so force them off for
683
# maximum compatibility. XXX: In future these should depend on the
684
# remote repository instance, but this is irrelevant until we perform
685
# reconcile via an RPC call.
686
self._reconcile_does_inventory_gc = False
687
self._reconcile_fixes_text_parents = False
688
self._reconcile_backsup_inventory = False
689
self.base = self.bzrdir.transport.base
690
# Additional places to query for data.
691
self._fallback_repositories = []
694
return "%s(%s)" % (self.__class__.__name__, self.base)
698
def abort_write_group(self, suppress_errors=False):
699
"""Complete a write group on the decorated repository.
701
Smart methods perform operations in a single step so this API
702
is not really applicable except as a compatibility thunk
703
for older plugins that don't use e.g. the CommitBuilder
706
:param suppress_errors: see Repository.abort_write_group.
709
return self._real_repository.abort_write_group(
710
suppress_errors=suppress_errors)
714
"""Decorate the real repository for now.
716
In the long term a full blown network facility is needed to avoid
717
creating a real repository object locally.
720
return self._real_repository.chk_bytes
722
def commit_write_group(self):
723
"""Complete a write group on the decorated repository.
725
Smart methods perform operations in a single step so this API
726
is not really applicable except as a compatibility thunk
727
for older plugins that don't use e.g. the CommitBuilder
731
return self._real_repository.commit_write_group()
733
def resume_write_group(self, tokens):
735
return self._real_repository.resume_write_group(tokens)
737
def suspend_write_group(self):
739
return self._real_repository.suspend_write_group()
741
def get_missing_parent_inventories(self, check_for_missing_texts=True):
743
return self._real_repository.get_missing_parent_inventories(
744
check_for_missing_texts=check_for_missing_texts)
746
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
748
return self._real_repository.get_rev_id_for_revno(
751
def get_rev_id_for_revno(self, revno, known_pair):
752
"""See Repository.get_rev_id_for_revno."""
753
path = self.bzrdir._path_for_remote_call(self._client)
755
if self._client._medium._is_remote_before((1, 17)):
756
return self._get_rev_id_for_revno_vfs(revno, known_pair)
757
response = self._call(
758
'Repository.get_rev_id_for_revno', path, revno, known_pair)
759
except errors.UnknownSmartMethod:
760
self._client._medium._remember_remote_is_before((1, 17))
761
return self._get_rev_id_for_revno_vfs(revno, known_pair)
762
if response[0] == 'ok':
763
return True, response[1]
764
elif response[0] == 'history-incomplete':
765
known_pair = response[1:3]
766
for fallback in self._fallback_repositories:
767
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
772
# Not found in any fallbacks
773
return False, known_pair
775
raise errors.UnexpectedSmartServerResponse(response)
777
def _ensure_real(self):
778
"""Ensure that there is a _real_repository set.
780
Used before calls to self._real_repository.
782
Note that _ensure_real causes many roundtrips to the server which are
783
not desirable, and prevents the use of smart one-roundtrip RPC's to
784
perform complex operations (such as accessing parent data, streaming
785
revisions etc). Adding calls to _ensure_real should only be done when
786
bringing up new functionality, adding fallbacks for smart methods that
787
require a fallback path, and never to replace an existing smart method
788
invocation. If in doubt chat to the bzr network team.
790
if self._real_repository is None:
791
if 'hpssvfs' in debug.debug_flags:
793
warning('VFS Repository access triggered\n%s',
794
''.join(traceback.format_stack()))
795
self._unstacked_provider.missing_keys.clear()
796
self.bzrdir._ensure_real()
797
self._set_real_repository(
798
self.bzrdir._real_bzrdir.open_repository())
800
def _translate_error(self, err, **context):
801
self.bzrdir._translate_error(err, repository=self, **context)
803
def find_text_key_references(self):
804
"""Find the text key references within the repository.
806
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
807
revision_ids. Each altered file-ids has the exact revision_ids that
808
altered it listed explicitly.
809
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
810
to whether they were referred to by the inventory of the
811
revision_id that they contain. The inventory texts from all present
812
revision ids are assessed to generate this report.
815
return self._real_repository.find_text_key_references()
817
def _generate_text_key_index(self):
818
"""Generate a new text key index for the repository.
820
This is an expensive function that will take considerable time to run.
822
:return: A dict mapping (file_id, revision_id) tuples to a list of
823
parents, also (file_id, revision_id) tuples.
826
return self._real_repository._generate_text_key_index()
828
def _get_revision_graph(self, revision_id):
829
"""Private method for using with old (< 1.2) servers to fallback."""
830
if revision_id is None:
832
elif revision.is_null(revision_id):
835
path = self.bzrdir._path_for_remote_call(self._client)
836
response = self._call_expecting_body(
837
'Repository.get_revision_graph', path, revision_id)
838
response_tuple, response_handler = response
839
if response_tuple[0] != 'ok':
840
raise errors.UnexpectedSmartServerResponse(response_tuple)
841
coded = response_handler.read_body_bytes()
843
# no revisions in this repository!
845
lines = coded.split('\n')
848
d = tuple(line.split())
849
revision_graph[d[0]] = d[1:]
851
return revision_graph
854
"""See Repository._get_sink()."""
855
return RemoteStreamSink(self)
857
def _get_source(self, to_format):
858
"""Return a source for streaming from this repository."""
859
return RemoteStreamSource(self, to_format)
862
def has_revision(self, revision_id):
863
"""True if this repository has a copy of the revision."""
864
# Copy of bzrlib.repository.Repository.has_revision
865
return revision_id in self.has_revisions((revision_id,))
868
def has_revisions(self, revision_ids):
869
"""Probe to find out the presence of multiple revisions.
871
:param revision_ids: An iterable of revision_ids.
872
:return: A set of the revision_ids that were present.
874
# Copy of bzrlib.repository.Repository.has_revisions
875
parent_map = self.get_parent_map(revision_ids)
876
result = set(parent_map)
877
if _mod_revision.NULL_REVISION in revision_ids:
878
result.add(_mod_revision.NULL_REVISION)
881
def _has_same_fallbacks(self, other_repo):
882
"""Returns true if the repositories have the same fallbacks."""
883
# XXX: copied from Repository; it should be unified into a base class
884
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
885
my_fb = self._fallback_repositories
886
other_fb = other_repo._fallback_repositories
887
if len(my_fb) != len(other_fb):
889
for f, g in zip(my_fb, other_fb):
890
if not f.has_same_location(g):
894
def has_same_location(self, other):
895
# TODO: Move to RepositoryBase and unify with the regular Repository
896
# one; unfortunately the tests rely on slightly different behaviour at
897
# present -- mbp 20090710
898
return (self.__class__ is other.__class__ and
899
self.bzrdir.transport.base == other.bzrdir.transport.base)
901
def get_graph(self, other_repository=None):
902
"""Return the graph for this repository format"""
903
parents_provider = self._make_parents_provider(other_repository)
904
return graph.Graph(parents_provider)
906
def gather_stats(self, revid=None, committers=None):
907
"""See Repository.gather_stats()."""
908
path = self.bzrdir._path_for_remote_call(self._client)
909
# revid can be None to indicate no revisions, not just NULL_REVISION
910
if revid is None or revision.is_null(revid):
914
if committers is None or not committers:
915
fmt_committers = 'no'
917
fmt_committers = 'yes'
918
response_tuple, response_handler = self._call_expecting_body(
919
'Repository.gather_stats', path, fmt_revid, fmt_committers)
920
if response_tuple[0] != 'ok':
921
raise errors.UnexpectedSmartServerResponse(response_tuple)
923
body = response_handler.read_body_bytes()
925
for line in body.split('\n'):
928
key, val_text = line.split(':')
929
if key in ('revisions', 'size', 'committers'):
930
result[key] = int(val_text)
931
elif key in ('firstrev', 'latestrev'):
932
values = val_text.split(' ')[1:]
933
result[key] = (float(values[0]), long(values[1]))
937
def find_branches(self, using=False):
938
"""See Repository.find_branches()."""
939
# should be an API call to the server.
941
return self._real_repository.find_branches(using=using)
943
def get_physical_lock_status(self):
944
"""See Repository.get_physical_lock_status()."""
945
# should be an API call to the server.
947
return self._real_repository.get_physical_lock_status()
949
def is_in_write_group(self):
950
"""Return True if there is an open write group.
952
write groups are only applicable locally for the smart server..
954
if self._real_repository:
955
return self._real_repository.is_in_write_group()
958
return self._lock_count >= 1
961
"""See Repository.is_shared()."""
962
path = self.bzrdir._path_for_remote_call(self._client)
963
response = self._call('Repository.is_shared', path)
964
if response[0] not in ('yes', 'no'):
965
raise SmartProtocolError('unexpected response code %s' % (response,))
966
return response[0] == 'yes'
968
def is_write_locked(self):
969
return self._lock_mode == 'w'
971
def _warn_if_deprecated(self, branch=None):
972
# If we have a real repository, the check will be done there, if we
973
# don't the check will be done remotely.
977
# wrong eventually - want a local lock cache context
978
if not self._lock_mode:
980
self._lock_mode = 'r'
982
self._unstacked_provider.enable_cache(cache_misses=True)
983
if self._real_repository is not None:
984
self._real_repository.lock_read()
985
for repo in self._fallback_repositories:
988
self._lock_count += 1
990
def _remote_lock_write(self, token):
991
path = self.bzrdir._path_for_remote_call(self._client)
994
err_context = {'token': token}
995
response = self._call('Repository.lock_write', path, token,
997
if response[0] == 'ok':
1001
raise errors.UnexpectedSmartServerResponse(response)
1003
def lock_write(self, token=None, _skip_rpc=False):
1004
if not self._lock_mode:
1005
self._note_lock('w')
1007
if self._lock_token is not None:
1008
if token != self._lock_token:
1009
raise errors.TokenMismatch(token, self._lock_token)
1010
self._lock_token = token
1012
self._lock_token = self._remote_lock_write(token)
1013
# if self._lock_token is None, then this is something like packs or
1014
# svn where we don't get to lock the repo, or a weave style repository
1015
# where we cannot lock it over the wire and attempts to do so will
1017
if self._real_repository is not None:
1018
self._real_repository.lock_write(token=self._lock_token)
1019
if token is not None:
1020
self._leave_lock = True
1022
self._leave_lock = False
1023
self._lock_mode = 'w'
1024
self._lock_count = 1
1025
cache_misses = self._real_repository is None
1026
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1027
for repo in self._fallback_repositories:
1028
# Writes don't affect fallback repos
1030
elif self._lock_mode == 'r':
1031
raise errors.ReadOnlyError(self)
1033
self._lock_count += 1
1034
return self._lock_token or None
1036
def leave_lock_in_place(self):
1037
if not self._lock_token:
1038
raise NotImplementedError(self.leave_lock_in_place)
1039
self._leave_lock = True
1041
def dont_leave_lock_in_place(self):
1042
if not self._lock_token:
1043
raise NotImplementedError(self.dont_leave_lock_in_place)
1044
self._leave_lock = False
1046
def _set_real_repository(self, repository):
1047
"""Set the _real_repository for this repository.
1049
:param repository: The repository to fallback to for non-hpss
1050
implemented operations.
1052
if self._real_repository is not None:
1053
# Replacing an already set real repository.
1054
# We cannot do this [currently] if the repository is locked -
1055
# synchronised state might be lost.
1056
if self.is_locked():
1057
raise AssertionError('_real_repository is already set')
1058
if isinstance(repository, RemoteRepository):
1059
raise AssertionError()
1060
self._real_repository = repository
1061
# three code paths happen here:
1062
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1063
# up stacking. In this case self._fallback_repositories is [], and the
1064
# real repo is already setup. Preserve the real repo and
1065
# RemoteRepository.add_fallback_repository will avoid adding
1067
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1068
# ensure_real is triggered from a branch, the real repository to
1069
# set already has a matching list with separate instances, but
1070
# as they are also RemoteRepositories we don't worry about making the
1071
# lists be identical.
1072
# 3) new servers, RemoteRepository.ensure_real is triggered before
1073
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1074
# and need to populate it.
1075
if (self._fallback_repositories and
1076
len(self._real_repository._fallback_repositories) !=
1077
len(self._fallback_repositories)):
1078
if len(self._real_repository._fallback_repositories):
1079
raise AssertionError(
1080
"cannot cleanly remove existing _fallback_repositories")
1081
for fb in self._fallback_repositories:
1082
self._real_repository.add_fallback_repository(fb)
1083
if self._lock_mode == 'w':
1084
# if we are already locked, the real repository must be able to
1085
# acquire the lock with our token.
1086
self._real_repository.lock_write(self._lock_token)
1087
elif self._lock_mode == 'r':
1088
self._real_repository.lock_read()
1090
def start_write_group(self):
1091
"""Start a write group on the decorated repository.
1093
Smart methods perform operations in a single step so this API
1094
is not really applicable except as a compatibility thunk
1095
for older plugins that don't use e.g. the CommitBuilder
1099
return self._real_repository.start_write_group()
1101
def _unlock(self, token):
1102
path = self.bzrdir._path_for_remote_call(self._client)
1104
# with no token the remote repository is not persistently locked.
1106
err_context = {'token': token}
1107
response = self._call('Repository.unlock', path, token,
1109
if response == ('ok',):
1112
raise errors.UnexpectedSmartServerResponse(response)
1114
@only_raises(errors.LockNotHeld, errors.LockBroken)
1116
if not self._lock_count:
1117
return lock.cant_unlock_not_held(self)
1118
self._lock_count -= 1
1119
if self._lock_count > 0:
1121
self._unstacked_provider.disable_cache()
1122
old_mode = self._lock_mode
1123
self._lock_mode = None
1125
# The real repository is responsible at present for raising an
1126
# exception if it's in an unfinished write group. However, it
1127
# normally will *not* actually remove the lock from disk - that's
1128
# done by the server on receiving the Repository.unlock call.
1129
# This is just to let the _real_repository stay up to date.
1130
if self._real_repository is not None:
1131
self._real_repository.unlock()
1133
# The rpc-level lock should be released even if there was a
1134
# problem releasing the vfs-based lock.
1136
# Only write-locked repositories need to make a remote method
1137
# call to perform the unlock.
1138
old_token = self._lock_token
1139
self._lock_token = None
1140
if not self._leave_lock:
1141
self._unlock(old_token)
1142
# Fallbacks are always 'lock_read()' so we don't pay attention to
1144
for repo in self._fallback_repositories:
1147
def break_lock(self):
1148
# should hand off to the network
1150
return self._real_repository.break_lock()
1152
def _get_tarball(self, compression):
1153
"""Return a TemporaryFile containing a repository tarball.
1155
Returns None if the server does not support sending tarballs.
1158
path = self.bzrdir._path_for_remote_call(self._client)
1160
response, protocol = self._call_expecting_body(
1161
'Repository.tarball', path, compression)
1162
except errors.UnknownSmartMethod:
1163
protocol.cancel_read_body()
1165
if response[0] == 'ok':
1166
# Extract the tarball and return it
1167
t = tempfile.NamedTemporaryFile()
1168
# TODO: rpc layer should read directly into it...
1169
t.write(protocol.read_body_bytes())
1172
raise errors.UnexpectedSmartServerResponse(response)
1174
def sprout(self, to_bzrdir, revision_id=None):
1175
# TODO: Option to control what format is created?
1177
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1179
dest_repo.fetch(self, revision_id=revision_id)
1182
### These methods are just thin shims to the VFS object for now.
1184
def revision_tree(self, revision_id):
1186
return self._real_repository.revision_tree(revision_id)
1188
def get_serializer_format(self):
1190
return self._real_repository.get_serializer_format()
1192
def get_commit_builder(self, branch, parents, config, timestamp=None,
1193
timezone=None, committer=None, revprops=None,
1195
# FIXME: It ought to be possible to call this without immediately
1196
# triggering _ensure_real. For now it's the easiest thing to do.
1198
real_repo = self._real_repository
1199
builder = real_repo.get_commit_builder(branch, parents,
1200
config, timestamp=timestamp, timezone=timezone,
1201
committer=committer, revprops=revprops, revision_id=revision_id)
1204
def add_fallback_repository(self, repository):
1205
"""Add a repository to use for looking up data not held locally.
1207
:param repository: A repository.
1209
if not self._format.supports_external_lookups:
1210
raise errors.UnstackableRepositoryFormat(
1211
self._format.network_name(), self.base)
1212
# We need to accumulate additional repositories here, to pass them in
1215
if self.is_locked():
1216
# We will call fallback.unlock() when we transition to the unlocked
1217
# state, so always add a lock here. If a caller passes us a locked
1218
# repository, they are responsible for unlocking it later.
1219
repository.lock_read()
1220
self._fallback_repositories.append(repository)
1221
# If self._real_repository was parameterised already (e.g. because a
1222
# _real_branch had its get_stacked_on_url method called), then the
1223
# repository to be added may already be in the _real_repositories list.
1224
if self._real_repository is not None:
1225
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1226
self._real_repository._fallback_repositories]
1227
if repository.bzrdir.root_transport.base not in fallback_locations:
1228
self._real_repository.add_fallback_repository(repository)
1230
def add_inventory(self, revid, inv, parents):
1232
return self._real_repository.add_inventory(revid, inv, parents)
1234
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1235
parents, basis_inv=None, propagate_caches=False):
1237
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1238
delta, new_revision_id, parents, basis_inv=basis_inv,
1239
propagate_caches=propagate_caches)
1241
def add_revision(self, rev_id, rev, inv=None, config=None):
1243
return self._real_repository.add_revision(
1244
rev_id, rev, inv=inv, config=config)
1247
def get_inventory(self, revision_id):
1249
return self._real_repository.get_inventory(revision_id)
1251
def iter_inventories(self, revision_ids, ordering=None):
1253
return self._real_repository.iter_inventories(revision_ids, ordering)
1256
def get_revision(self, revision_id):
1258
return self._real_repository.get_revision(revision_id)
1260
def get_transaction(self):
1262
return self._real_repository.get_transaction()
1265
def clone(self, a_bzrdir, revision_id=None):
1267
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1269
def make_working_trees(self):
1270
"""See Repository.make_working_trees"""
1272
return self._real_repository.make_working_trees()
1274
def refresh_data(self):
1275
"""Re-read any data needed to to synchronise with disk.
1277
This method is intended to be called after another repository instance
1278
(such as one used by a smart server) has inserted data into the
1279
repository. It may not be called during a write group, but may be
1280
called at any other time.
1282
if self.is_in_write_group():
1283
raise errors.InternalBzrError(
1284
"May not refresh_data while in a write group.")
1285
if self._real_repository is not None:
1286
self._real_repository.refresh_data()
1288
def revision_ids_to_search_result(self, result_set):
1289
"""Convert a set of revision ids to a graph SearchResult."""
1290
result_parents = set()
1291
for parents in self.get_graph().get_parent_map(
1292
result_set).itervalues():
1293
result_parents.update(parents)
1294
included_keys = result_set.intersection(result_parents)
1295
start_keys = result_set.difference(included_keys)
1296
exclude_keys = result_parents.difference(result_set)
1297
result = graph.SearchResult(start_keys, exclude_keys,
1298
len(result_set), result_set)
1302
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1303
"""Return the revision ids that other has that this does not.
1305
These are returned in topological order.
1307
revision_id: only return revision ids included by revision_id.
1309
return repository.InterRepository.get(
1310
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1312
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1314
# No base implementation to use as RemoteRepository is not a subclass
1315
# of Repository; so this is a copy of Repository.fetch().
1316
if fetch_spec is not None and revision_id is not None:
1317
raise AssertionError(
1318
"fetch_spec and revision_id are mutually exclusive.")
1319
if self.is_in_write_group():
1320
raise errors.InternalBzrError(
1321
"May not fetch while in a write group.")
1322
# fast path same-url fetch operations
1323
if (self.has_same_location(source)
1324
and fetch_spec is None
1325
and self._has_same_fallbacks(source)):
1326
# check that last_revision is in 'from' and then return a
1328
if (revision_id is not None and
1329
not revision.is_null(revision_id)):
1330
self.get_revision(revision_id)
1332
# if there is no specific appropriate InterRepository, this will get
1333
# the InterRepository base class, which raises an
1334
# IncompatibleRepositories when asked to fetch.
1335
inter = repository.InterRepository.get(source, self)
1336
return inter.fetch(revision_id=revision_id, pb=pb,
1337
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1339
def create_bundle(self, target, base, fileobj, format=None):
1341
self._real_repository.create_bundle(target, base, fileobj, format)
1344
def get_ancestry(self, revision_id, topo_sorted=True):
1346
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1348
def fileids_altered_by_revision_ids(self, revision_ids):
1350
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1352
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1354
return self._real_repository._get_versioned_file_checker(
1355
revisions, revision_versions_cache)
1357
def iter_files_bytes(self, desired_files):
1358
"""See Repository.iter_file_bytes.
1361
return self._real_repository.iter_files_bytes(desired_files)
1363
def get_parent_map(self, revision_ids):
1364
"""See bzrlib.Graph.get_parent_map()."""
1365
return self._make_parents_provider().get_parent_map(revision_ids)
1367
def _get_parent_map_rpc(self, keys):
1368
"""Helper for get_parent_map that performs the RPC."""
1369
medium = self._client._medium
1370
if medium._is_remote_before((1, 2)):
1371
# We already found out that the server can't understand
1372
# Repository.get_parent_map requests, so just fetch the whole
1375
# Note that this reads the whole graph, when only some keys are
1376
# wanted. On this old server there's no way (?) to get them all
1377
# in one go, and the user probably will have seen a warning about
1378
# the server being old anyhow.
1379
rg = self._get_revision_graph(None)
1380
# There is an API discrepancy between get_parent_map and
1381
# get_revision_graph. Specifically, a "key:()" pair in
1382
# get_revision_graph just means a node has no parents. For
1383
# "get_parent_map" it means the node is a ghost. So fix up the
1384
# graph to correct this.
1385
# https://bugs.launchpad.net/bzr/+bug/214894
1386
# There is one other "bug" which is that ghosts in
1387
# get_revision_graph() are not returned at all. But we won't worry
1388
# about that for now.
1389
for node_id, parent_ids in rg.iteritems():
1390
if parent_ids == ():
1391
rg[node_id] = (NULL_REVISION,)
1392
rg[NULL_REVISION] = ()
1397
raise ValueError('get_parent_map(None) is not valid')
1398
if NULL_REVISION in keys:
1399
keys.discard(NULL_REVISION)
1400
found_parents = {NULL_REVISION:()}
1402
return found_parents
1405
# TODO(Needs analysis): We could assume that the keys being requested
1406
# from get_parent_map are in a breadth first search, so typically they
1407
# will all be depth N from some common parent, and we don't have to
1408
# have the server iterate from the root parent, but rather from the
1409
# keys we're searching; and just tell the server the keyspace we
1410
# already have; but this may be more traffic again.
1412
# Transform self._parents_map into a search request recipe.
1413
# TODO: Manage this incrementally to avoid covering the same path
1414
# repeatedly. (The server will have to on each request, but the less
1415
# work done the better).
1417
# Negative caching notes:
1418
# new server sends missing when a request including the revid
1419
# 'include-missing:' is present in the request.
1420
# missing keys are serialised as missing:X, and we then call
1421
# provider.note_missing(X) for-all X
1422
parents_map = self._unstacked_provider.get_cached_map()
1423
if parents_map is None:
1424
# Repository is not locked, so there's no cache.
1426
# start_set is all the keys in the cache
1427
start_set = set(parents_map)
1428
# result set is all the references to keys in the cache
1429
result_parents = set()
1430
for parents in parents_map.itervalues():
1431
result_parents.update(parents)
1432
stop_keys = result_parents.difference(start_set)
1433
# We don't need to send ghosts back to the server as a position to
1435
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1436
key_count = len(parents_map)
1437
if (NULL_REVISION in result_parents
1438
and NULL_REVISION in self._unstacked_provider.missing_keys):
1439
# If we pruned NULL_REVISION from the stop_keys because it's also
1440
# in our cache of "missing" keys we need to increment our key count
1441
# by 1, because the reconsitituted SearchResult on the server will
1442
# still consider NULL_REVISION to be an included key.
1444
included_keys = start_set.intersection(result_parents)
1445
start_set.difference_update(included_keys)
1446
recipe = ('manual', start_set, stop_keys, key_count)
1447
body = self._serialise_search_recipe(recipe)
1448
path = self.bzrdir._path_for_remote_call(self._client)
1450
if type(key) is not str:
1452
"key %r not a plain string" % (key,))
1453
verb = 'Repository.get_parent_map'
1454
args = (path, 'include-missing:') + tuple(keys)
1456
response = self._call_with_body_bytes_expecting_body(
1458
except errors.UnknownSmartMethod:
1459
# Server does not support this method, so get the whole graph.
1460
# Worse, we have to force a disconnection, because the server now
1461
# doesn't realise it has a body on the wire to consume, so the
1462
# only way to recover is to abandon the connection.
1464
'Server is too old for fast get_parent_map, reconnecting. '
1465
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1467
# To avoid having to disconnect repeatedly, we keep track of the
1468
# fact the server doesn't understand remote methods added in 1.2.
1469
medium._remember_remote_is_before((1, 2))
1470
# Recurse just once and we should use the fallback code.
1471
return self._get_parent_map_rpc(keys)
1472
response_tuple, response_handler = response
1473
if response_tuple[0] not in ['ok']:
1474
response_handler.cancel_read_body()
1475
raise errors.UnexpectedSmartServerResponse(response_tuple)
1476
if response_tuple[0] == 'ok':
1477
coded = bz2.decompress(response_handler.read_body_bytes())
1479
# no revisions found
1481
lines = coded.split('\n')
1484
d = tuple(line.split())
1486
revision_graph[d[0]] = d[1:]
1489
if d[0].startswith('missing:'):
1491
self._unstacked_provider.note_missing_key(revid)
1493
# no parents - so give the Graph result
1495
revision_graph[d[0]] = (NULL_REVISION,)
1496
return revision_graph
1499
def get_signature_text(self, revision_id):
1501
return self._real_repository.get_signature_text(revision_id)
1504
def _get_inventory_xml(self, revision_id):
1506
return self._real_repository._get_inventory_xml(revision_id)
1508
def reconcile(self, other=None, thorough=False):
1510
return self._real_repository.reconcile(other=other, thorough=thorough)
1512
def all_revision_ids(self):
1514
return self._real_repository.all_revision_ids()
1517
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1519
return self._real_repository.get_deltas_for_revisions(revisions,
1520
specific_fileids=specific_fileids)
1523
def get_revision_delta(self, revision_id, specific_fileids=None):
1525
return self._real_repository.get_revision_delta(revision_id,
1526
specific_fileids=specific_fileids)
1529
def revision_trees(self, revision_ids):
1531
return self._real_repository.revision_trees(revision_ids)
1534
def get_revision_reconcile(self, revision_id):
1536
return self._real_repository.get_revision_reconcile(revision_id)
1539
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1541
return self._real_repository.check(revision_ids=revision_ids,
1542
callback_refs=callback_refs, check_repo=check_repo)
1544
def copy_content_into(self, destination, revision_id=None):
1546
return self._real_repository.copy_content_into(
1547
destination, revision_id=revision_id)
1549
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1550
# get a tarball of the remote repository, and copy from that into the
1552
from bzrlib import osutils
1554
# TODO: Maybe a progress bar while streaming the tarball?
1555
note("Copying repository content as tarball...")
1556
tar_file = self._get_tarball('bz2')
1557
if tar_file is None:
1559
destination = to_bzrdir.create_repository()
1561
tar = tarfile.open('repository', fileobj=tar_file,
1563
tmpdir = osutils.mkdtemp()
1565
_extract_tar(tar, tmpdir)
1566
tmp_bzrdir = BzrDir.open(tmpdir)
1567
tmp_repo = tmp_bzrdir.open_repository()
1568
tmp_repo.copy_content_into(destination, revision_id)
1570
osutils.rmtree(tmpdir)
1574
# TODO: Suggestion from john: using external tar is much faster than
1575
# python's tarfile library, but it may not work on windows.
1578
def inventories(self):
1579
"""Decorate the real repository for now.
1581
In the long term a full blown network facility is needed to
1582
avoid creating a real repository object locally.
1585
return self._real_repository.inventories
1588
def pack(self, hint=None):
1589
"""Compress the data within the repository.
1591
This is not currently implemented within the smart server.
1594
return self._real_repository.pack(hint=hint)
1597
def revisions(self):
1598
"""Decorate the real repository for now.
1600
In the short term this should become a real object to intercept graph
1603
In the long term a full blown network facility is needed.
1606
return self._real_repository.revisions
1608
def set_make_working_trees(self, new_value):
1610
new_value_str = "True"
1612
new_value_str = "False"
1613
path = self.bzrdir._path_for_remote_call(self._client)
1615
response = self._call(
1616
'Repository.set_make_working_trees', path, new_value_str)
1617
except errors.UnknownSmartMethod:
1619
self._real_repository.set_make_working_trees(new_value)
1621
if response[0] != 'ok':
1622
raise errors.UnexpectedSmartServerResponse(response)
1625
def signatures(self):
1626
"""Decorate the real repository for now.
1628
In the long term a full blown network facility is needed to avoid
1629
creating a real repository object locally.
1632
return self._real_repository.signatures
1635
def sign_revision(self, revision_id, gpg_strategy):
1637
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1641
"""Decorate the real repository for now.
1643
In the long term a full blown network facility is needed to avoid
1644
creating a real repository object locally.
1647
return self._real_repository.texts
1650
def get_revisions(self, revision_ids):
1652
return self._real_repository.get_revisions(revision_ids)
1654
def supports_rich_root(self):
1655
return self._format.rich_root_data
1657
def iter_reverse_revision_history(self, revision_id):
1659
return self._real_repository.iter_reverse_revision_history(revision_id)
1662
def _serializer(self):
1663
return self._format._serializer
1665
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1667
return self._real_repository.store_revision_signature(
1668
gpg_strategy, plaintext, revision_id)
1670
def add_signature_text(self, revision_id, signature):
1672
return self._real_repository.add_signature_text(revision_id, signature)
1674
def has_signature_for_revision_id(self, revision_id):
1676
return self._real_repository.has_signature_for_revision_id(revision_id)
1678
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1680
return self._real_repository.item_keys_introduced_by(revision_ids,
1681
_files_pb=_files_pb)
1683
def revision_graph_can_have_wrong_parents(self):
1684
# The answer depends on the remote repo format.
1686
return self._real_repository.revision_graph_can_have_wrong_parents()
1688
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1690
return self._real_repository._find_inconsistent_revision_parents(
1693
def _check_for_inconsistent_revision_parents(self):
1695
return self._real_repository._check_for_inconsistent_revision_parents()
1697
def _make_parents_provider(self, other=None):
1698
providers = [self._unstacked_provider]
1699
if other is not None:
1700
providers.insert(0, other)
1701
providers.extend(r._make_parents_provider() for r in
1702
self._fallback_repositories)
1703
return graph.StackedParentsProvider(providers)
1705
def _serialise_search_recipe(self, recipe):
1706
"""Serialise a graph search recipe.
1708
:param recipe: A search recipe (start, stop, count).
1709
:return: Serialised bytes.
1711
start_keys = ' '.join(recipe[1])
1712
stop_keys = ' '.join(recipe[2])
1713
count = str(recipe[3])
1714
return '\n'.join((start_keys, stop_keys, count))
1716
def _serialise_search_result(self, search_result):
1717
if isinstance(search_result, graph.PendingAncestryResult):
1718
parts = ['ancestry-of']
1719
parts.extend(search_result.heads)
1721
recipe = search_result.get_recipe()
1722
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1723
return '\n'.join(parts)
1726
path = self.bzrdir._path_for_remote_call(self._client)
1728
response = self._call('PackRepository.autopack', path)
1729
except errors.UnknownSmartMethod:
1731
self._real_repository._pack_collection.autopack()
1734
if response[0] != 'ok':
1735
raise errors.UnexpectedSmartServerResponse(response)
1738
class RemoteStreamSink(repository.StreamSink):
1740
def _insert_real(self, stream, src_format, resume_tokens):
1741
self.target_repo._ensure_real()
1742
sink = self.target_repo._real_repository._get_sink()
1743
result = sink.insert_stream(stream, src_format, resume_tokens)
1745
self.target_repo.autopack()
1748
def insert_stream(self, stream, src_format, resume_tokens):
1749
target = self.target_repo
1750
target._unstacked_provider.missing_keys.clear()
1751
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1752
if target._lock_token:
1753
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1754
lock_args = (target._lock_token or '',)
1756
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1758
client = target._client
1759
medium = client._medium
1760
path = target.bzrdir._path_for_remote_call(client)
1761
# Probe for the verb to use with an empty stream before sending the
1762
# real stream to it. We do this both to avoid the risk of sending a
1763
# large request that is then rejected, and because we don't want to
1764
# implement a way to buffer, rewind, or restart the stream.
1766
for verb, required_version in candidate_calls:
1767
if medium._is_remote_before(required_version):
1770
# We've already done the probing (and set _is_remote_before) on
1771
# a previous insert.
1774
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1776
response = client.call_with_body_stream(
1777
(verb, path, '') + lock_args, byte_stream)
1778
except errors.UnknownSmartMethod:
1779
medium._remember_remote_is_before(required_version)
1785
return self._insert_real(stream, src_format, resume_tokens)
1786
self._last_inv_record = None
1787
self._last_substream = None
1788
if required_version < (1, 19):
1789
# Remote side doesn't support inventory deltas. Wrap the stream to
1790
# make sure we don't send any. If the stream contains inventory
1791
# deltas we'll interrupt the smart insert_stream request and
1793
stream = self._stop_stream_if_inventory_delta(stream)
1794
byte_stream = smart_repo._stream_to_byte_stream(
1796
resume_tokens = ' '.join(resume_tokens)
1797
response = client.call_with_body_stream(
1798
(verb, path, resume_tokens) + lock_args, byte_stream)
1799
if response[0][0] not in ('ok', 'missing-basis'):
1800
raise errors.UnexpectedSmartServerResponse(response)
1801
if self._last_substream is not None:
1802
# The stream included an inventory-delta record, but the remote
1803
# side isn't new enough to support them. So we need to send the
1804
# rest of the stream via VFS.
1805
self.target_repo.refresh_data()
1806
return self._resume_stream_with_vfs(response, src_format)
1807
if response[0][0] == 'missing-basis':
1808
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1809
resume_tokens = tokens
1810
return resume_tokens, set(missing_keys)
1812
self.target_repo.refresh_data()
1815
def _resume_stream_with_vfs(self, response, src_format):
1816
"""Resume sending a stream via VFS, first resending the record and
1817
substream that couldn't be sent via an insert_stream verb.
1819
if response[0][0] == 'missing-basis':
1820
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1821
# Ignore missing_keys, we haven't finished inserting yet
1824
def resume_substream():
1825
# Yield the substream that was interrupted.
1826
for record in self._last_substream:
1828
self._last_substream = None
1829
def resume_stream():
1830
# Finish sending the interrupted substream
1831
yield ('inventory-deltas', resume_substream())
1832
# Then simply continue sending the rest of the stream.
1833
for substream_kind, substream in self._last_stream:
1834
yield substream_kind, substream
1835
return self._insert_real(resume_stream(), src_format, tokens)
1837
def _stop_stream_if_inventory_delta(self, stream):
1838
"""Normally this just lets the original stream pass-through unchanged.
1840
However if any 'inventory-deltas' substream occurs it will stop
1841
streaming, and store the interrupted substream and stream in
1842
self._last_substream and self._last_stream so that the stream can be
1843
resumed by _resume_stream_with_vfs.
1846
stream_iter = iter(stream)
1847
for substream_kind, substream in stream_iter:
1848
if substream_kind == 'inventory-deltas':
1849
self._last_substream = substream
1850
self._last_stream = stream_iter
1853
yield substream_kind, substream
1856
class RemoteStreamSource(repository.StreamSource):
1857
"""Stream data from a remote server."""
1859
def get_stream(self, search):
1860
if (self.from_repository._fallback_repositories and
1861
self.to_format._fetch_order == 'topological'):
1862
return self._real_stream(self.from_repository, search)
1865
repos = [self.from_repository]
1871
repos.extend(repo._fallback_repositories)
1872
sources.append(repo)
1873
return self.missing_parents_chain(search, sources)
1875
def get_stream_for_missing_keys(self, missing_keys):
1876
self.from_repository._ensure_real()
1877
real_repo = self.from_repository._real_repository
1878
real_source = real_repo._get_source(self.to_format)
1879
return real_source.get_stream_for_missing_keys(missing_keys)
1881
def _real_stream(self, repo, search):
1882
"""Get a stream for search from repo.
1884
This never called RemoteStreamSource.get_stream, and is a heler
1885
for RemoteStreamSource._get_stream to allow getting a stream
1886
reliably whether fallback back because of old servers or trying
1887
to stream from a non-RemoteRepository (which the stacked support
1890
source = repo._get_source(self.to_format)
1891
if isinstance(source, RemoteStreamSource):
1893
source = repo._real_repository._get_source(self.to_format)
1894
return source.get_stream(search)
1896
def _get_stream(self, repo, search):
1897
"""Core worker to get a stream from repo for search.
1899
This is used by both get_stream and the stacking support logic. It
1900
deliberately gets a stream for repo which does not need to be
1901
self.from_repository. In the event that repo is not Remote, or
1902
cannot do a smart stream, a fallback is made to the generic
1903
repository._get_stream() interface, via self._real_stream.
1905
In the event of stacking, streams from _get_stream will not
1906
contain all the data for search - this is normal (see get_stream).
1908
:param repo: A repository.
1909
:param search: A search.
1911
# Fallbacks may be non-smart
1912
if not isinstance(repo, RemoteRepository):
1913
return self._real_stream(repo, search)
1914
client = repo._client
1915
medium = client._medium
1916
path = repo.bzrdir._path_for_remote_call(client)
1917
search_bytes = repo._serialise_search_result(search)
1918
args = (path, self.to_format.network_name())
1920
('Repository.get_stream_1.19', (1, 19)),
1921
('Repository.get_stream', (1, 13))]
1923
for verb, version in candidate_verbs:
1924
if medium._is_remote_before(version):
1927
response = repo._call_with_body_bytes_expecting_body(
1928
verb, args, search_bytes)
1929
except errors.UnknownSmartMethod:
1930
medium._remember_remote_is_before(version)
1932
response_tuple, response_handler = response
1936
return self._real_stream(repo, search)
1937
if response_tuple[0] != 'ok':
1938
raise errors.UnexpectedSmartServerResponse(response_tuple)
1939
byte_stream = response_handler.read_streamed_body()
1940
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1941
if src_format.network_name() != repo._format.network_name():
1942
raise AssertionError(
1943
"Mismatched RemoteRepository and stream src %r, %r" % (
1944
src_format.network_name(), repo._format.network_name()))
1947
def missing_parents_chain(self, search, sources):
1948
"""Chain multiple streams together to handle stacking.
1950
:param search: The overall search to satisfy with streams.
1951
:param sources: A list of Repository objects to query.
1953
self.from_serialiser = self.from_repository._format._serializer
1954
self.seen_revs = set()
1955
self.referenced_revs = set()
1956
# If there are heads in the search, or the key count is > 0, we are not
1958
while not search.is_empty() and len(sources) > 1:
1959
source = sources.pop(0)
1960
stream = self._get_stream(source, search)
1961
for kind, substream in stream:
1962
if kind != 'revisions':
1963
yield kind, substream
1965
yield kind, self.missing_parents_rev_handler(substream)
1966
search = search.refine(self.seen_revs, self.referenced_revs)
1967
self.seen_revs = set()
1968
self.referenced_revs = set()
1969
if not search.is_empty():
1970
for kind, stream in self._get_stream(sources[0], search):
1973
def missing_parents_rev_handler(self, substream):
1974
for content in substream:
1975
revision_bytes = content.get_bytes_as('fulltext')
1976
revision = self.from_serialiser.read_revision_from_string(
1978
self.seen_revs.add(content.key[-1])
1979
self.referenced_revs.update(revision.parent_ids)
1983
class RemoteBranchLockableFiles(LockableFiles):
1984
"""A 'LockableFiles' implementation that talks to a smart server.
1986
This is not a public interface class.
1989
def __init__(self, bzrdir, _client):
1990
self.bzrdir = bzrdir
1991
self._client = _client
1992
self._need_find_modes = True
1993
LockableFiles.__init__(
1994
self, bzrdir.get_branch_transport(None),
1995
'lock', lockdir.LockDir)
1997
def _find_modes(self):
1998
# RemoteBranches don't let the client set the mode of control files.
1999
self._dir_mode = None
2000
self._file_mode = None
2003
class RemoteBranchFormat(branch.BranchFormat):
2005
def __init__(self, network_name=None):
2006
super(RemoteBranchFormat, self).__init__()
2007
self._matchingbzrdir = RemoteBzrDirFormat()
2008
self._matchingbzrdir.set_branch_format(self)
2009
self._custom_format = None
2010
self._network_name = network_name
2012
def __eq__(self, other):
2013
return (isinstance(other, RemoteBranchFormat) and
2014
self.__dict__ == other.__dict__)
2016
def _ensure_real(self):
2017
if self._custom_format is None:
2018
self._custom_format = branch.network_format_registry.get(
2021
def get_format_description(self):
2023
return 'Remote: ' + self._custom_format.get_format_description()
2025
def network_name(self):
2026
return self._network_name
2028
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2029
return a_bzrdir.open_branch(name=name,
2030
ignore_fallbacks=ignore_fallbacks)
2032
def _vfs_initialize(self, a_bzrdir, name):
2033
# Initialisation when using a local bzrdir object, or a non-vfs init
2034
# method is not available on the server.
2035
# self._custom_format is always set - the start of initialize ensures
2037
if isinstance(a_bzrdir, RemoteBzrDir):
2038
a_bzrdir._ensure_real()
2039
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2042
# We assume the bzrdir is parameterised; it may not be.
2043
result = self._custom_format.initialize(a_bzrdir, name)
2044
if (isinstance(a_bzrdir, RemoteBzrDir) and
2045
not isinstance(result, RemoteBranch)):
2046
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2050
def initialize(self, a_bzrdir, name=None):
2051
# 1) get the network name to use.
2052
if self._custom_format:
2053
network_name = self._custom_format.network_name()
2055
# Select the current bzrlib default and ask for that.
2056
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2057
reference_format = reference_bzrdir_format.get_branch_format()
2058
self._custom_format = reference_format
2059
network_name = reference_format.network_name()
2060
# Being asked to create on a non RemoteBzrDir:
2061
if not isinstance(a_bzrdir, RemoteBzrDir):
2062
return self._vfs_initialize(a_bzrdir, name=name)
2063
medium = a_bzrdir._client._medium
2064
if medium._is_remote_before((1, 13)):
2065
return self._vfs_initialize(a_bzrdir, name=name)
2066
# Creating on a remote bzr dir.
2067
# 2) try direct creation via RPC
2068
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2069
if name is not None:
2070
# XXX JRV20100304: Support creating colocated branches
2071
raise errors.NoColocatedBranchSupport(self)
2072
verb = 'BzrDir.create_branch'
2074
response = a_bzrdir._call(verb, path, network_name)
2075
except errors.UnknownSmartMethod:
2076
# Fallback - use vfs methods
2077
medium._remember_remote_is_before((1, 13))
2078
return self._vfs_initialize(a_bzrdir, name=name)
2079
if response[0] != 'ok':
2080
raise errors.UnexpectedSmartServerResponse(response)
2081
# Turn the response into a RemoteRepository object.
2082
format = RemoteBranchFormat(network_name=response[1])
2083
repo_format = response_tuple_to_repo_format(response[3:])
2084
if response[2] == '':
2085
repo_bzrdir = a_bzrdir
2087
repo_bzrdir = RemoteBzrDir(
2088
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2090
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2091
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2092
format=format, setup_stacking=False, name=name)
2093
# XXX: We know this is a new branch, so it must have revno 0, revid
2094
# NULL_REVISION. Creating the branch locked would make this be unable
2095
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2096
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2097
return remote_branch
2099
def make_tags(self, branch):
2101
return self._custom_format.make_tags(branch)
2103
def supports_tags(self):
2104
# Remote branches might support tags, but we won't know until we
2105
# access the real remote branch.
2107
return self._custom_format.supports_tags()
2109
def supports_stacking(self):
2111
return self._custom_format.supports_stacking()
2113
def supports_set_append_revisions_only(self):
2115
return self._custom_format.supports_set_append_revisions_only()
2118
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2119
"""Branch stored on a server accessed by HPSS RPC.
2121
At the moment most operations are mapped down to simple file operations.
2124
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2125
_client=None, format=None, setup_stacking=True, name=None):
2126
"""Create a RemoteBranch instance.
2128
:param real_branch: An optional local implementation of the branch
2129
format, usually accessing the data via the VFS.
2130
:param _client: Private parameter for testing.
2131
:param format: A RemoteBranchFormat object, None to create one
2132
automatically. If supplied it should have a network_name already
2134
:param setup_stacking: If True make an RPC call to determine the
2135
stacked (or not) status of the branch. If False assume the branch
2137
:param name: Colocated branch name
2139
# We intentionally don't call the parent class's __init__, because it
2140
# will try to assign to self.tags, which is a property in this subclass.
2141
# And the parent's __init__ doesn't do much anyway.
2142
self.bzrdir = remote_bzrdir
2143
if _client is not None:
2144
self._client = _client
2146
self._client = remote_bzrdir._client
2147
self.repository = remote_repository
2148
if real_branch is not None:
2149
self._real_branch = real_branch
2150
# Give the remote repository the matching real repo.
2151
real_repo = self._real_branch.repository
2152
if isinstance(real_repo, RemoteRepository):
2153
real_repo._ensure_real()
2154
real_repo = real_repo._real_repository
2155
self.repository._set_real_repository(real_repo)
2156
# Give the branch the remote repository to let fast-pathing happen.
2157
self._real_branch.repository = self.repository
2159
self._real_branch = None
2160
# Fill out expected attributes of branch for bzrlib API users.
2161
self._clear_cached_state()
2162
self.base = self.bzrdir.root_transport.base
2164
self._control_files = None
2165
self._lock_mode = None
2166
self._lock_token = None
2167
self._repo_lock_token = None
2168
self._lock_count = 0
2169
self._leave_lock = False
2170
# Setup a format: note that we cannot call _ensure_real until all the
2171
# attributes above are set: This code cannot be moved higher up in this
2174
self._format = RemoteBranchFormat()
2175
if real_branch is not None:
2176
self._format._network_name = \
2177
self._real_branch._format.network_name()
2179
self._format = format
2180
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2181
# branch.open_branch method.
2182
self._real_ignore_fallbacks = not setup_stacking
2183
if not self._format._network_name:
2184
# Did not get from open_branchV2 - old server.
2186
self._format._network_name = \
2187
self._real_branch._format.network_name()
2188
self.tags = self._format.make_tags(self)
2189
# The base class init is not called, so we duplicate this:
2190
hooks = branch.Branch.hooks['open']
2193
self._is_stacked = False
2195
self._setup_stacking()
2197
def _setup_stacking(self):
2198
# configure stacking into the remote repository, by reading it from
2201
fallback_url = self.get_stacked_on_url()
2202
except (errors.NotStacked, errors.UnstackableBranchFormat,
2203
errors.UnstackableRepositoryFormat), e:
2205
self._is_stacked = True
2206
self._activate_fallback_location(fallback_url)
2208
def _get_config(self):
2209
return RemoteBranchConfig(self)
2211
def _get_real_transport(self):
2212
# if we try vfs access, return the real branch's vfs transport
2214
return self._real_branch._transport
2216
_transport = property(_get_real_transport)
2219
return "%s(%s)" % (self.__class__.__name__, self.base)
2223
def _ensure_real(self):
2224
"""Ensure that there is a _real_branch set.
2226
Used before calls to self._real_branch.
2228
if self._real_branch is None:
2229
if not vfs.vfs_enabled():
2230
raise AssertionError('smart server vfs must be enabled '
2231
'to use vfs implementation')
2232
self.bzrdir._ensure_real()
2233
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2234
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2235
if self.repository._real_repository is None:
2236
# Give the remote repository the matching real repo.
2237
real_repo = self._real_branch.repository
2238
if isinstance(real_repo, RemoteRepository):
2239
real_repo._ensure_real()
2240
real_repo = real_repo._real_repository
2241
self.repository._set_real_repository(real_repo)
2242
# Give the real branch the remote repository to let fast-pathing
2244
self._real_branch.repository = self.repository
2245
if self._lock_mode == 'r':
2246
self._real_branch.lock_read()
2247
elif self._lock_mode == 'w':
2248
self._real_branch.lock_write(token=self._lock_token)
2250
def _translate_error(self, err, **context):
2251
self.repository._translate_error(err, branch=self, **context)
2253
def _clear_cached_state(self):
2254
super(RemoteBranch, self)._clear_cached_state()
2255
if self._real_branch is not None:
2256
self._real_branch._clear_cached_state()
2258
def _clear_cached_state_of_remote_branch_only(self):
2259
"""Like _clear_cached_state, but doesn't clear the cache of
2262
This is useful when falling back to calling a method of
2263
self._real_branch that changes state. In that case the underlying
2264
branch changes, so we need to invalidate this RemoteBranch's cache of
2265
it. However, there's no need to invalidate the _real_branch's cache
2266
too, in fact doing so might harm performance.
2268
super(RemoteBranch, self)._clear_cached_state()
2271
def control_files(self):
2272
# Defer actually creating RemoteBranchLockableFiles until its needed,
2273
# because it triggers an _ensure_real that we otherwise might not need.
2274
if self._control_files is None:
2275
self._control_files = RemoteBranchLockableFiles(
2276
self.bzrdir, self._client)
2277
return self._control_files
2279
def _get_checkout_format(self):
2281
return self._real_branch._get_checkout_format()
2283
def get_physical_lock_status(self):
2284
"""See Branch.get_physical_lock_status()."""
2285
# should be an API call to the server, as branches must be lockable.
2287
return self._real_branch.get_physical_lock_status()
2289
def get_stacked_on_url(self):
2290
"""Get the URL this branch is stacked against.
2292
:raises NotStacked: If the branch is not stacked.
2293
:raises UnstackableBranchFormat: If the branch does not support
2295
:raises UnstackableRepositoryFormat: If the repository does not support
2299
# there may not be a repository yet, so we can't use
2300
# self._translate_error, so we can't use self._call either.
2301
response = self._client.call('Branch.get_stacked_on_url',
2302
self._remote_path())
2303
except errors.ErrorFromSmartServer, err:
2304
# there may not be a repository yet, so we can't call through
2305
# its _translate_error
2306
_translate_error(err, branch=self)
2307
except errors.UnknownSmartMethod, err:
2309
return self._real_branch.get_stacked_on_url()
2310
if response[0] != 'ok':
2311
raise errors.UnexpectedSmartServerResponse(response)
2314
def set_stacked_on_url(self, url):
2315
branch.Branch.set_stacked_on_url(self, url)
2317
self._is_stacked = False
2319
self._is_stacked = True
2321
def _vfs_get_tags_bytes(self):
2323
return self._real_branch._get_tags_bytes()
2325
def _get_tags_bytes(self):
2326
medium = self._client._medium
2327
if medium._is_remote_before((1, 13)):
2328
return self._vfs_get_tags_bytes()
2330
response = self._call('Branch.get_tags_bytes', self._remote_path())
2331
except errors.UnknownSmartMethod:
2332
medium._remember_remote_is_before((1, 13))
2333
return self._vfs_get_tags_bytes()
2336
def _vfs_set_tags_bytes(self, bytes):
2338
return self._real_branch._set_tags_bytes(bytes)
2340
def _set_tags_bytes(self, bytes):
2341
medium = self._client._medium
2342
if medium._is_remote_before((1, 18)):
2343
self._vfs_set_tags_bytes(bytes)
2347
self._remote_path(), self._lock_token, self._repo_lock_token)
2348
response = self._call_with_body_bytes(
2349
'Branch.set_tags_bytes', args, bytes)
2350
except errors.UnknownSmartMethod:
2351
medium._remember_remote_is_before((1, 18))
2352
self._vfs_set_tags_bytes(bytes)
2354
def lock_read(self):
2355
self.repository.lock_read()
2356
if not self._lock_mode:
2357
self._note_lock('r')
2358
self._lock_mode = 'r'
2359
self._lock_count = 1
2360
if self._real_branch is not None:
2361
self._real_branch.lock_read()
2363
self._lock_count += 1
2365
def _remote_lock_write(self, token):
2367
branch_token = repo_token = ''
2369
branch_token = token
2370
repo_token = self.repository.lock_write()
2371
self.repository.unlock()
2372
err_context = {'token': token}
2373
response = self._call(
2374
'Branch.lock_write', self._remote_path(), branch_token,
2375
repo_token or '', **err_context)
2376
if response[0] != 'ok':
2377
raise errors.UnexpectedSmartServerResponse(response)
2378
ok, branch_token, repo_token = response
2379
return branch_token, repo_token
2381
def lock_write(self, token=None):
2382
if not self._lock_mode:
2383
self._note_lock('w')
2384
# Lock the branch and repo in one remote call.
2385
remote_tokens = self._remote_lock_write(token)
2386
self._lock_token, self._repo_lock_token = remote_tokens
2387
if not self._lock_token:
2388
raise SmartProtocolError('Remote server did not return a token!')
2389
# Tell the self.repository object that it is locked.
2390
self.repository.lock_write(
2391
self._repo_lock_token, _skip_rpc=True)
2393
if self._real_branch is not None:
2394
self._real_branch.lock_write(token=self._lock_token)
2395
if token is not None:
2396
self._leave_lock = True
2398
self._leave_lock = False
2399
self._lock_mode = 'w'
2400
self._lock_count = 1
2401
elif self._lock_mode == 'r':
2402
raise errors.ReadOnlyTransaction
2404
if token is not None:
2405
# A token was given to lock_write, and we're relocking, so
2406
# check that the given token actually matches the one we
2408
if token != self._lock_token:
2409
raise errors.TokenMismatch(token, self._lock_token)
2410
self._lock_count += 1
2411
# Re-lock the repository too.
2412
self.repository.lock_write(self._repo_lock_token)
2413
return self._lock_token or None
2415
def _unlock(self, branch_token, repo_token):
2416
err_context = {'token': str((branch_token, repo_token))}
2417
response = self._call(
2418
'Branch.unlock', self._remote_path(), branch_token,
2419
repo_token or '', **err_context)
2420
if response == ('ok',):
2422
raise errors.UnexpectedSmartServerResponse(response)
2424
@only_raises(errors.LockNotHeld, errors.LockBroken)
2427
self._lock_count -= 1
2428
if not self._lock_count:
2429
self._clear_cached_state()
2430
mode = self._lock_mode
2431
self._lock_mode = None
2432
if self._real_branch is not None:
2433
if (not self._leave_lock and mode == 'w' and
2434
self._repo_lock_token):
2435
# If this RemoteBranch will remove the physical lock
2436
# for the repository, make sure the _real_branch
2437
# doesn't do it first. (Because the _real_branch's
2438
# repository is set to be the RemoteRepository.)
2439
self._real_branch.repository.leave_lock_in_place()
2440
self._real_branch.unlock()
2442
# Only write-locked branched need to make a remote method
2443
# call to perform the unlock.
2445
if not self._lock_token:
2446
raise AssertionError('Locked, but no token!')
2447
branch_token = self._lock_token
2448
repo_token = self._repo_lock_token
2449
self._lock_token = None
2450
self._repo_lock_token = None
2451
if not self._leave_lock:
2452
self._unlock(branch_token, repo_token)
2454
self.repository.unlock()
2456
def break_lock(self):
2458
return self._real_branch.break_lock()
2460
def leave_lock_in_place(self):
2461
if not self._lock_token:
2462
raise NotImplementedError(self.leave_lock_in_place)
2463
self._leave_lock = True
2465
def dont_leave_lock_in_place(self):
2466
if not self._lock_token:
2467
raise NotImplementedError(self.dont_leave_lock_in_place)
2468
self._leave_lock = False
2471
def get_rev_id(self, revno, history=None):
2473
return _mod_revision.NULL_REVISION
2474
last_revision_info = self.last_revision_info()
2475
ok, result = self.repository.get_rev_id_for_revno(
2476
revno, last_revision_info)
2479
missing_parent = result[1]
2480
# Either the revision named by the server is missing, or its parent
2481
# is. Call get_parent_map to determine which, so that we report a
2483
parent_map = self.repository.get_parent_map([missing_parent])
2484
if missing_parent in parent_map:
2485
missing_parent = parent_map[missing_parent]
2486
raise errors.RevisionNotPresent(missing_parent, self.repository)
2488
def _last_revision_info(self):
2489
response = self._call('Branch.last_revision_info', self._remote_path())
2490
if response[0] != 'ok':
2491
raise SmartProtocolError('unexpected response code %s' % (response,))
2492
revno = int(response[1])
2493
last_revision = response[2]
2494
return (revno, last_revision)
2496
def _gen_revision_history(self):
2497
"""See Branch._gen_revision_history()."""
2498
if self._is_stacked:
2500
return self._real_branch._gen_revision_history()
2501
response_tuple, response_handler = self._call_expecting_body(
2502
'Branch.revision_history', self._remote_path())
2503
if response_tuple[0] != 'ok':
2504
raise errors.UnexpectedSmartServerResponse(response_tuple)
2505
result = response_handler.read_body_bytes().split('\x00')
2510
def _remote_path(self):
2511
return self.bzrdir._path_for_remote_call(self._client)
2513
def _set_last_revision_descendant(self, revision_id, other_branch,
2514
allow_diverged=False, allow_overwrite_descendant=False):
2515
# This performs additional work to meet the hook contract; while its
2516
# undesirable, we have to synthesise the revno to call the hook, and
2517
# not calling the hook is worse as it means changes can't be prevented.
2518
# Having calculated this though, we can't just call into
2519
# set_last_revision_info as a simple call, because there is a set_rh
2520
# hook that some folk may still be using.
2521
old_revno, old_revid = self.last_revision_info()
2522
history = self._lefthand_history(revision_id)
2523
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2524
err_context = {'other_branch': other_branch}
2525
response = self._call('Branch.set_last_revision_ex',
2526
self._remote_path(), self._lock_token, self._repo_lock_token,
2527
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2529
self._clear_cached_state()
2530
if len(response) != 3 and response[0] != 'ok':
2531
raise errors.UnexpectedSmartServerResponse(response)
2532
new_revno, new_revision_id = response[1:]
2533
self._last_revision_info_cache = new_revno, new_revision_id
2534
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2535
if self._real_branch is not None:
2536
cache = new_revno, new_revision_id
2537
self._real_branch._last_revision_info_cache = cache
2539
def _set_last_revision(self, revision_id):
2540
old_revno, old_revid = self.last_revision_info()
2541
# This performs additional work to meet the hook contract; while its
2542
# undesirable, we have to synthesise the revno to call the hook, and
2543
# not calling the hook is worse as it means changes can't be prevented.
2544
# Having calculated this though, we can't just call into
2545
# set_last_revision_info as a simple call, because there is a set_rh
2546
# hook that some folk may still be using.
2547
history = self._lefthand_history(revision_id)
2548
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2549
self._clear_cached_state()
2550
response = self._call('Branch.set_last_revision',
2551
self._remote_path(), self._lock_token, self._repo_lock_token,
2553
if response != ('ok',):
2554
raise errors.UnexpectedSmartServerResponse(response)
2555
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2558
def set_revision_history(self, rev_history):
2559
# Send just the tip revision of the history; the server will generate
2560
# the full history from that. If the revision doesn't exist in this
2561
# branch, NoSuchRevision will be raised.
2562
if rev_history == []:
2565
rev_id = rev_history[-1]
2566
self._set_last_revision(rev_id)
2567
for hook in branch.Branch.hooks['set_rh']:
2568
hook(self, rev_history)
2569
self._cache_revision_history(rev_history)
2571
def _get_parent_location(self):
2572
medium = self._client._medium
2573
if medium._is_remote_before((1, 13)):
2574
return self._vfs_get_parent_location()
2576
response = self._call('Branch.get_parent', self._remote_path())
2577
except errors.UnknownSmartMethod:
2578
medium._remember_remote_is_before((1, 13))
2579
return self._vfs_get_parent_location()
2580
if len(response) != 1:
2581
raise errors.UnexpectedSmartServerResponse(response)
2582
parent_location = response[0]
2583
if parent_location == '':
2585
return parent_location
2587
def _vfs_get_parent_location(self):
2589
return self._real_branch._get_parent_location()
2591
def _set_parent_location(self, url):
2592
medium = self._client._medium
2593
if medium._is_remote_before((1, 15)):
2594
return self._vfs_set_parent_location(url)
2596
call_url = url or ''
2597
if type(call_url) is not str:
2598
raise AssertionError('url must be a str or None (%s)' % url)
2599
response = self._call('Branch.set_parent_location',
2600
self._remote_path(), self._lock_token, self._repo_lock_token,
2602
except errors.UnknownSmartMethod:
2603
medium._remember_remote_is_before((1, 15))
2604
return self._vfs_set_parent_location(url)
2606
raise errors.UnexpectedSmartServerResponse(response)
2608
def _vfs_set_parent_location(self, url):
2610
return self._real_branch._set_parent_location(url)
2613
def pull(self, source, overwrite=False, stop_revision=None,
2615
self._clear_cached_state_of_remote_branch_only()
2617
return self._real_branch.pull(
2618
source, overwrite=overwrite, stop_revision=stop_revision,
2619
_override_hook_target=self, **kwargs)
2622
def push(self, target, overwrite=False, stop_revision=None):
2624
return self._real_branch.push(
2625
target, overwrite=overwrite, stop_revision=stop_revision,
2626
_override_hook_source_branch=self)
2628
def is_locked(self):
2629
return self._lock_count >= 1
2632
def revision_id_to_revno(self, revision_id):
2634
return self._real_branch.revision_id_to_revno(revision_id)
2637
def set_last_revision_info(self, revno, revision_id):
2638
# XXX: These should be returned by the set_last_revision_info verb
2639
old_revno, old_revid = self.last_revision_info()
2640
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2641
revision_id = ensure_null(revision_id)
2643
response = self._call('Branch.set_last_revision_info',
2644
self._remote_path(), self._lock_token, self._repo_lock_token,
2645
str(revno), revision_id)
2646
except errors.UnknownSmartMethod:
2648
self._clear_cached_state_of_remote_branch_only()
2649
self._real_branch.set_last_revision_info(revno, revision_id)
2650
self._last_revision_info_cache = revno, revision_id
2652
if response == ('ok',):
2653
self._clear_cached_state()
2654
self._last_revision_info_cache = revno, revision_id
2655
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2656
# Update the _real_branch's cache too.
2657
if self._real_branch is not None:
2658
cache = self._last_revision_info_cache
2659
self._real_branch._last_revision_info_cache = cache
2661
raise errors.UnexpectedSmartServerResponse(response)
2664
def generate_revision_history(self, revision_id, last_rev=None,
2666
medium = self._client._medium
2667
if not medium._is_remote_before((1, 6)):
2668
# Use a smart method for 1.6 and above servers
2670
self._set_last_revision_descendant(revision_id, other_branch,
2671
allow_diverged=True, allow_overwrite_descendant=True)
2673
except errors.UnknownSmartMethod:
2674
medium._remember_remote_is_before((1, 6))
2675
self._clear_cached_state_of_remote_branch_only()
2676
self.set_revision_history(self._lefthand_history(revision_id,
2677
last_rev=last_rev,other_branch=other_branch))
2679
def set_push_location(self, location):
2681
return self._real_branch.set_push_location(location)
2684
class RemoteConfig(object):
2685
"""A Config that reads and writes from smart verbs.
2687
It is a low-level object that considers config data to be name/value pairs
2688
that may be associated with a section. Assigning meaning to the these
2689
values is done at higher levels like bzrlib.config.TreeConfig.
2692
def get_option(self, name, section=None, default=None):
2693
"""Return the value associated with a named option.
2695
:param name: The name of the value
2696
:param section: The section the option is in (if any)
2697
:param default: The value to return if the value is not set
2698
:return: The value or default value
2701
configobj = self._get_configobj()
2703
section_obj = configobj
2706
section_obj = configobj[section]
2709
return section_obj.get(name, default)
2710
except errors.UnknownSmartMethod:
2711
return self._vfs_get_option(name, section, default)
2713
def _response_to_configobj(self, response):
2714
if len(response[0]) and response[0][0] != 'ok':
2715
raise errors.UnexpectedSmartServerResponse(response)
2716
lines = response[1].read_body_bytes().splitlines()
2717
return config.ConfigObj(lines, encoding='utf-8')
2720
class RemoteBranchConfig(RemoteConfig):
2721
"""A RemoteConfig for Branches."""
2723
def __init__(self, branch):
2724
self._branch = branch
2726
def _get_configobj(self):
2727
path = self._branch._remote_path()
2728
response = self._branch._client.call_expecting_body(
2729
'Branch.get_config_file', path)
2730
return self._response_to_configobj(response)
2732
def set_option(self, value, name, section=None):
2733
"""Set the value associated with a named option.
2735
:param value: The value to set
2736
:param name: The name of the value to set
2737
:param section: The section the option is in (if any)
2739
medium = self._branch._client._medium
2740
if medium._is_remote_before((1, 14)):
2741
return self._vfs_set_option(value, name, section)
2743
path = self._branch._remote_path()
2744
response = self._branch._client.call('Branch.set_config_option',
2745
path, self._branch._lock_token, self._branch._repo_lock_token,
2746
value.encode('utf8'), name, section or '')
2747
except errors.UnknownSmartMethod:
2748
medium._remember_remote_is_before((1, 14))
2749
return self._vfs_set_option(value, name, section)
2751
raise errors.UnexpectedSmartServerResponse(response)
2753
def _real_object(self):
2754
self._branch._ensure_real()
2755
return self._branch._real_branch
2757
def _vfs_set_option(self, value, name, section=None):
2758
return self._real_object()._get_config().set_option(
2759
value, name, section)
2762
class RemoteBzrDirConfig(RemoteConfig):
2763
"""A RemoteConfig for BzrDirs."""
2765
def __init__(self, bzrdir):
2766
self._bzrdir = bzrdir
2768
def _get_configobj(self):
2769
medium = self._bzrdir._client._medium
2770
verb = 'BzrDir.get_config_file'
2771
if medium._is_remote_before((1, 15)):
2772
raise errors.UnknownSmartMethod(verb)
2773
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2774
response = self._bzrdir._call_expecting_body(
2776
return self._response_to_configobj(response)
2778
def _vfs_get_option(self, name, section, default):
2779
return self._real_object()._get_config().get_option(
2780
name, section, default)
2782
def set_option(self, value, name, section=None):
2783
"""Set the value associated with a named option.
2785
:param value: The value to set
2786
:param name: The name of the value to set
2787
:param section: The section the option is in (if any)
2789
return self._real_object()._get_config().set_option(
2790
value, name, section)
2792
def _real_object(self):
2793
self._bzrdir._ensure_real()
2794
return self._bzrdir._real_bzrdir
2798
def _extract_tar(tar, to_dir):
2799
"""Extract all the contents of a tarfile object.
2801
A replacement for extractall, which is not present in python2.4
2804
tar.extract(tarinfo, to_dir)
2807
def _translate_error(err, **context):
2808
"""Translate an ErrorFromSmartServer into a more useful error.
2810
Possible context keys:
2818
If the error from the server doesn't match a known pattern, then
2819
UnknownErrorFromSmartServer is raised.
2823
return context[name]
2824
except KeyError, key_err:
2825
mutter('Missing key %r in context %r', key_err.args[0], context)
2828
"""Get the path from the context if present, otherwise use first error
2832
return context['path']
2833
except KeyError, key_err:
2835
return err.error_args[0]
2836
except IndexError, idx_err:
2838
'Missing key %r in context %r', key_err.args[0], context)
2841
if err.error_verb == 'IncompatibleRepositories':
2842
raise errors.IncompatibleRepositories(err.error_args[0],
2843
err.error_args[1], err.error_args[2])
2844
elif err.error_verb == 'NoSuchRevision':
2845
raise NoSuchRevision(find('branch'), err.error_args[0])
2846
elif err.error_verb == 'nosuchrevision':
2847
raise NoSuchRevision(find('repository'), err.error_args[0])
2848
elif err.error_verb == 'nobranch':
2849
if len(err.error_args) >= 1:
2850
extra = err.error_args[0]
2853
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2855
elif err.error_verb == 'norepository':
2856
raise errors.NoRepositoryPresent(find('bzrdir'))
2857
elif err.error_verb == 'LockContention':
2858
raise errors.LockContention('(remote lock)')
2859
elif err.error_verb == 'UnlockableTransport':
2860
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2861
elif err.error_verb == 'LockFailed':
2862
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2863
elif err.error_verb == 'TokenMismatch':
2864
raise errors.TokenMismatch(find('token'), '(remote token)')
2865
elif err.error_verb == 'Diverged':
2866
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2867
elif err.error_verb == 'TipChangeRejected':
2868
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2869
elif err.error_verb == 'UnstackableBranchFormat':
2870
raise errors.UnstackableBranchFormat(*err.error_args)
2871
elif err.error_verb == 'UnstackableRepositoryFormat':
2872
raise errors.UnstackableRepositoryFormat(*err.error_args)
2873
elif err.error_verb == 'NotStacked':
2874
raise errors.NotStacked(branch=find('branch'))
2875
elif err.error_verb == 'PermissionDenied':
2877
if len(err.error_args) >= 2:
2878
extra = err.error_args[1]
2881
raise errors.PermissionDenied(path, extra=extra)
2882
elif err.error_verb == 'ReadError':
2884
raise errors.ReadError(path)
2885
elif err.error_verb == 'NoSuchFile':
2887
raise errors.NoSuchFile(path)
2888
elif err.error_verb == 'FileExists':
2889
raise errors.FileExists(err.error_args[0])
2890
elif err.error_verb == 'DirectoryNotEmpty':
2891
raise errors.DirectoryNotEmpty(err.error_args[0])
2892
elif err.error_verb == 'ShortReadvError':
2893
args = err.error_args
2894
raise errors.ShortReadvError(
2895
args[0], int(args[1]), int(args[2]), int(args[3]))
2896
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2897
encoding = str(err.error_args[0]) # encoding must always be a string
2898
val = err.error_args[1]
2899
start = int(err.error_args[2])
2900
end = int(err.error_args[3])
2901
reason = str(err.error_args[4]) # reason must always be a string
2902
if val.startswith('u:'):
2903
val = val[2:].decode('utf-8')
2904
elif val.startswith('s:'):
2905
val = val[2:].decode('base64')
2906
if err.error_verb == 'UnicodeDecodeError':
2907
raise UnicodeDecodeError(encoding, val, start, end, reason)
2908
elif err.error_verb == 'UnicodeEncodeError':
2909
raise UnicodeEncodeError(encoding, val, start, end, reason)
2910
elif err.error_verb == 'ReadOnlyError':
2911
raise errors.TransportNotPossible('readonly transport')
2912
raise errors.UnknownErrorFromSmartServer(err)