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,
1237
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1238
delta, new_revision_id, parents)
1240
def add_revision(self, rev_id, rev, inv=None, config=None):
1242
return self._real_repository.add_revision(
1243
rev_id, rev, inv=inv, config=config)
1246
def get_inventory(self, revision_id):
1248
return self._real_repository.get_inventory(revision_id)
1250
def iter_inventories(self, revision_ids, ordering=None):
1252
return self._real_repository.iter_inventories(revision_ids, ordering)
1255
def get_revision(self, revision_id):
1257
return self._real_repository.get_revision(revision_id)
1259
def get_transaction(self):
1261
return self._real_repository.get_transaction()
1264
def clone(self, a_bzrdir, revision_id=None):
1266
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1268
def make_working_trees(self):
1269
"""See Repository.make_working_trees"""
1271
return self._real_repository.make_working_trees()
1273
def refresh_data(self):
1274
"""Re-read any data needed to to synchronise with disk.
1276
This method is intended to be called after another repository instance
1277
(such as one used by a smart server) has inserted data into the
1278
repository. It may not be called during a write group, but may be
1279
called at any other time.
1281
if self.is_in_write_group():
1282
raise errors.InternalBzrError(
1283
"May not refresh_data while in a write group.")
1284
if self._real_repository is not None:
1285
self._real_repository.refresh_data()
1287
def revision_ids_to_search_result(self, result_set):
1288
"""Convert a set of revision ids to a graph SearchResult."""
1289
result_parents = set()
1290
for parents in self.get_graph().get_parent_map(
1291
result_set).itervalues():
1292
result_parents.update(parents)
1293
included_keys = result_set.intersection(result_parents)
1294
start_keys = result_set.difference(included_keys)
1295
exclude_keys = result_parents.difference(result_set)
1296
result = graph.SearchResult(start_keys, exclude_keys,
1297
len(result_set), result_set)
1301
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1302
"""Return the revision ids that other has that this does not.
1304
These are returned in topological order.
1306
revision_id: only return revision ids included by revision_id.
1308
return repository.InterRepository.get(
1309
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1311
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1313
# No base implementation to use as RemoteRepository is not a subclass
1314
# of Repository; so this is a copy of Repository.fetch().
1315
if fetch_spec is not None and revision_id is not None:
1316
raise AssertionError(
1317
"fetch_spec and revision_id are mutually exclusive.")
1318
if self.is_in_write_group():
1319
raise errors.InternalBzrError(
1320
"May not fetch while in a write group.")
1321
# fast path same-url fetch operations
1322
if (self.has_same_location(source)
1323
and fetch_spec is None
1324
and self._has_same_fallbacks(source)):
1325
# check that last_revision is in 'from' and then return a
1327
if (revision_id is not None and
1328
not revision.is_null(revision_id)):
1329
self.get_revision(revision_id)
1331
# if there is no specific appropriate InterRepository, this will get
1332
# the InterRepository base class, which raises an
1333
# IncompatibleRepositories when asked to fetch.
1334
inter = repository.InterRepository.get(source, self)
1335
return inter.fetch(revision_id=revision_id, pb=pb,
1336
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1338
def create_bundle(self, target, base, fileobj, format=None):
1340
self._real_repository.create_bundle(target, base, fileobj, format)
1343
def get_ancestry(self, revision_id, topo_sorted=True):
1345
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1347
def fileids_altered_by_revision_ids(self, revision_ids):
1349
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1351
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1353
return self._real_repository._get_versioned_file_checker(
1354
revisions, revision_versions_cache)
1356
def iter_files_bytes(self, desired_files):
1357
"""See Repository.iter_file_bytes.
1360
return self._real_repository.iter_files_bytes(desired_files)
1362
def get_parent_map(self, revision_ids):
1363
"""See bzrlib.Graph.get_parent_map()."""
1364
return self._make_parents_provider().get_parent_map(revision_ids)
1366
def _get_parent_map_rpc(self, keys):
1367
"""Helper for get_parent_map that performs the RPC."""
1368
medium = self._client._medium
1369
if medium._is_remote_before((1, 2)):
1370
# We already found out that the server can't understand
1371
# Repository.get_parent_map requests, so just fetch the whole
1374
# Note that this reads the whole graph, when only some keys are
1375
# wanted. On this old server there's no way (?) to get them all
1376
# in one go, and the user probably will have seen a warning about
1377
# the server being old anyhow.
1378
rg = self._get_revision_graph(None)
1379
# There is an API discrepancy between get_parent_map and
1380
# get_revision_graph. Specifically, a "key:()" pair in
1381
# get_revision_graph just means a node has no parents. For
1382
# "get_parent_map" it means the node is a ghost. So fix up the
1383
# graph to correct this.
1384
# https://bugs.launchpad.net/bzr/+bug/214894
1385
# There is one other "bug" which is that ghosts in
1386
# get_revision_graph() are not returned at all. But we won't worry
1387
# about that for now.
1388
for node_id, parent_ids in rg.iteritems():
1389
if parent_ids == ():
1390
rg[node_id] = (NULL_REVISION,)
1391
rg[NULL_REVISION] = ()
1396
raise ValueError('get_parent_map(None) is not valid')
1397
if NULL_REVISION in keys:
1398
keys.discard(NULL_REVISION)
1399
found_parents = {NULL_REVISION:()}
1401
return found_parents
1404
# TODO(Needs analysis): We could assume that the keys being requested
1405
# from get_parent_map are in a breadth first search, so typically they
1406
# will all be depth N from some common parent, and we don't have to
1407
# have the server iterate from the root parent, but rather from the
1408
# keys we're searching; and just tell the server the keyspace we
1409
# already have; but this may be more traffic again.
1411
# Transform self._parents_map into a search request recipe.
1412
# TODO: Manage this incrementally to avoid covering the same path
1413
# repeatedly. (The server will have to on each request, but the less
1414
# work done the better).
1416
# Negative caching notes:
1417
# new server sends missing when a request including the revid
1418
# 'include-missing:' is present in the request.
1419
# missing keys are serialised as missing:X, and we then call
1420
# provider.note_missing(X) for-all X
1421
parents_map = self._unstacked_provider.get_cached_map()
1422
if parents_map is None:
1423
# Repository is not locked, so there's no cache.
1425
# start_set is all the keys in the cache
1426
start_set = set(parents_map)
1427
# result set is all the references to keys in the cache
1428
result_parents = set()
1429
for parents in parents_map.itervalues():
1430
result_parents.update(parents)
1431
stop_keys = result_parents.difference(start_set)
1432
# We don't need to send ghosts back to the server as a position to
1434
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1435
key_count = len(parents_map)
1436
if (NULL_REVISION in result_parents
1437
and NULL_REVISION in self._unstacked_provider.missing_keys):
1438
# If we pruned NULL_REVISION from the stop_keys because it's also
1439
# in our cache of "missing" keys we need to increment our key count
1440
# by 1, because the reconsitituted SearchResult on the server will
1441
# still consider NULL_REVISION to be an included key.
1443
included_keys = start_set.intersection(result_parents)
1444
start_set.difference_update(included_keys)
1445
recipe = ('manual', start_set, stop_keys, key_count)
1446
body = self._serialise_search_recipe(recipe)
1447
path = self.bzrdir._path_for_remote_call(self._client)
1449
if type(key) is not str:
1451
"key %r not a plain string" % (key,))
1452
verb = 'Repository.get_parent_map'
1453
args = (path, 'include-missing:') + tuple(keys)
1455
response = self._call_with_body_bytes_expecting_body(
1457
except errors.UnknownSmartMethod:
1458
# Server does not support this method, so get the whole graph.
1459
# Worse, we have to force a disconnection, because the server now
1460
# doesn't realise it has a body on the wire to consume, so the
1461
# only way to recover is to abandon the connection.
1463
'Server is too old for fast get_parent_map, reconnecting. '
1464
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1466
# To avoid having to disconnect repeatedly, we keep track of the
1467
# fact the server doesn't understand remote methods added in 1.2.
1468
medium._remember_remote_is_before((1, 2))
1469
# Recurse just once and we should use the fallback code.
1470
return self._get_parent_map_rpc(keys)
1471
response_tuple, response_handler = response
1472
if response_tuple[0] not in ['ok']:
1473
response_handler.cancel_read_body()
1474
raise errors.UnexpectedSmartServerResponse(response_tuple)
1475
if response_tuple[0] == 'ok':
1476
coded = bz2.decompress(response_handler.read_body_bytes())
1478
# no revisions found
1480
lines = coded.split('\n')
1483
d = tuple(line.split())
1485
revision_graph[d[0]] = d[1:]
1488
if d[0].startswith('missing:'):
1490
self._unstacked_provider.note_missing_key(revid)
1492
# no parents - so give the Graph result
1494
revision_graph[d[0]] = (NULL_REVISION,)
1495
return revision_graph
1498
def get_signature_text(self, revision_id):
1500
return self._real_repository.get_signature_text(revision_id)
1503
def _get_inventory_xml(self, revision_id):
1505
return self._real_repository._get_inventory_xml(revision_id)
1507
def reconcile(self, other=None, thorough=False):
1509
return self._real_repository.reconcile(other=other, thorough=thorough)
1511
def all_revision_ids(self):
1513
return self._real_repository.all_revision_ids()
1516
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1518
return self._real_repository.get_deltas_for_revisions(revisions,
1519
specific_fileids=specific_fileids)
1522
def get_revision_delta(self, revision_id, specific_fileids=None):
1524
return self._real_repository.get_revision_delta(revision_id,
1525
specific_fileids=specific_fileids)
1528
def revision_trees(self, revision_ids):
1530
return self._real_repository.revision_trees(revision_ids)
1533
def get_revision_reconcile(self, revision_id):
1535
return self._real_repository.get_revision_reconcile(revision_id)
1538
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1540
return self._real_repository.check(revision_ids=revision_ids,
1541
callback_refs=callback_refs, check_repo=check_repo)
1543
def copy_content_into(self, destination, revision_id=None):
1545
return self._real_repository.copy_content_into(
1546
destination, revision_id=revision_id)
1548
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1549
# get a tarball of the remote repository, and copy from that into the
1551
from bzrlib import osutils
1553
# TODO: Maybe a progress bar while streaming the tarball?
1554
note("Copying repository content as tarball...")
1555
tar_file = self._get_tarball('bz2')
1556
if tar_file is None:
1558
destination = to_bzrdir.create_repository()
1560
tar = tarfile.open('repository', fileobj=tar_file,
1562
tmpdir = osutils.mkdtemp()
1564
_extract_tar(tar, tmpdir)
1565
tmp_bzrdir = BzrDir.open(tmpdir)
1566
tmp_repo = tmp_bzrdir.open_repository()
1567
tmp_repo.copy_content_into(destination, revision_id)
1569
osutils.rmtree(tmpdir)
1573
# TODO: Suggestion from john: using external tar is much faster than
1574
# python's tarfile library, but it may not work on windows.
1577
def inventories(self):
1578
"""Decorate the real repository for now.
1580
In the long term a full blown network facility is needed to
1581
avoid creating a real repository object locally.
1584
return self._real_repository.inventories
1587
def pack(self, hint=None):
1588
"""Compress the data within the repository.
1590
This is not currently implemented within the smart server.
1593
return self._real_repository.pack(hint=hint)
1596
def revisions(self):
1597
"""Decorate the real repository for now.
1599
In the short term this should become a real object to intercept graph
1602
In the long term a full blown network facility is needed.
1605
return self._real_repository.revisions
1607
def set_make_working_trees(self, new_value):
1609
new_value_str = "True"
1611
new_value_str = "False"
1612
path = self.bzrdir._path_for_remote_call(self._client)
1614
response = self._call(
1615
'Repository.set_make_working_trees', path, new_value_str)
1616
except errors.UnknownSmartMethod:
1618
self._real_repository.set_make_working_trees(new_value)
1620
if response[0] != 'ok':
1621
raise errors.UnexpectedSmartServerResponse(response)
1624
def signatures(self):
1625
"""Decorate the real repository for now.
1627
In the long term a full blown network facility is needed to avoid
1628
creating a real repository object locally.
1631
return self._real_repository.signatures
1634
def sign_revision(self, revision_id, gpg_strategy):
1636
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1640
"""Decorate the real repository for now.
1642
In the long term a full blown network facility is needed to avoid
1643
creating a real repository object locally.
1646
return self._real_repository.texts
1649
def get_revisions(self, revision_ids):
1651
return self._real_repository.get_revisions(revision_ids)
1653
def supports_rich_root(self):
1654
return self._format.rich_root_data
1656
def iter_reverse_revision_history(self, revision_id):
1658
return self._real_repository.iter_reverse_revision_history(revision_id)
1661
def _serializer(self):
1662
return self._format._serializer
1664
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1666
return self._real_repository.store_revision_signature(
1667
gpg_strategy, plaintext, revision_id)
1669
def add_signature_text(self, revision_id, signature):
1671
return self._real_repository.add_signature_text(revision_id, signature)
1673
def has_signature_for_revision_id(self, revision_id):
1675
return self._real_repository.has_signature_for_revision_id(revision_id)
1677
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1679
return self._real_repository.item_keys_introduced_by(revision_ids,
1680
_files_pb=_files_pb)
1682
def revision_graph_can_have_wrong_parents(self):
1683
# The answer depends on the remote repo format.
1685
return self._real_repository.revision_graph_can_have_wrong_parents()
1687
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1689
return self._real_repository._find_inconsistent_revision_parents(
1692
def _check_for_inconsistent_revision_parents(self):
1694
return self._real_repository._check_for_inconsistent_revision_parents()
1696
def _make_parents_provider(self, other=None):
1697
providers = [self._unstacked_provider]
1698
if other is not None:
1699
providers.insert(0, other)
1700
providers.extend(r._make_parents_provider() for r in
1701
self._fallback_repositories)
1702
return graph.StackedParentsProvider(providers)
1704
def _serialise_search_recipe(self, recipe):
1705
"""Serialise a graph search recipe.
1707
:param recipe: A search recipe (start, stop, count).
1708
:return: Serialised bytes.
1710
start_keys = ' '.join(recipe[1])
1711
stop_keys = ' '.join(recipe[2])
1712
count = str(recipe[3])
1713
return '\n'.join((start_keys, stop_keys, count))
1715
def _serialise_search_result(self, search_result):
1716
if isinstance(search_result, graph.PendingAncestryResult):
1717
parts = ['ancestry-of']
1718
parts.extend(search_result.heads)
1720
recipe = search_result.get_recipe()
1721
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1722
return '\n'.join(parts)
1725
path = self.bzrdir._path_for_remote_call(self._client)
1727
response = self._call('PackRepository.autopack', path)
1728
except errors.UnknownSmartMethod:
1730
self._real_repository._pack_collection.autopack()
1733
if response[0] != 'ok':
1734
raise errors.UnexpectedSmartServerResponse(response)
1737
class RemoteStreamSink(repository.StreamSink):
1739
def _insert_real(self, stream, src_format, resume_tokens):
1740
self.target_repo._ensure_real()
1741
sink = self.target_repo._real_repository._get_sink()
1742
result = sink.insert_stream(stream, src_format, resume_tokens)
1744
self.target_repo.autopack()
1747
def insert_stream(self, stream, src_format, resume_tokens):
1748
target = self.target_repo
1749
target._unstacked_provider.missing_keys.clear()
1750
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1751
if target._lock_token:
1752
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1753
lock_args = (target._lock_token or '',)
1755
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1757
client = target._client
1758
medium = client._medium
1759
path = target.bzrdir._path_for_remote_call(client)
1760
# Probe for the verb to use with an empty stream before sending the
1761
# real stream to it. We do this both to avoid the risk of sending a
1762
# large request that is then rejected, and because we don't want to
1763
# implement a way to buffer, rewind, or restart the stream.
1765
for verb, required_version in candidate_calls:
1766
if medium._is_remote_before(required_version):
1769
# We've already done the probing (and set _is_remote_before) on
1770
# a previous insert.
1773
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1775
response = client.call_with_body_stream(
1776
(verb, path, '') + lock_args, byte_stream)
1777
except errors.UnknownSmartMethod:
1778
medium._remember_remote_is_before(required_version)
1784
return self._insert_real(stream, src_format, resume_tokens)
1785
self._last_inv_record = None
1786
self._last_substream = None
1787
if required_version < (1, 19):
1788
# Remote side doesn't support inventory deltas. Wrap the stream to
1789
# make sure we don't send any. If the stream contains inventory
1790
# deltas we'll interrupt the smart insert_stream request and
1792
stream = self._stop_stream_if_inventory_delta(stream)
1793
byte_stream = smart_repo._stream_to_byte_stream(
1795
resume_tokens = ' '.join(resume_tokens)
1796
response = client.call_with_body_stream(
1797
(verb, path, resume_tokens) + lock_args, byte_stream)
1798
if response[0][0] not in ('ok', 'missing-basis'):
1799
raise errors.UnexpectedSmartServerResponse(response)
1800
if self._last_substream is not None:
1801
# The stream included an inventory-delta record, but the remote
1802
# side isn't new enough to support them. So we need to send the
1803
# rest of the stream via VFS.
1804
self.target_repo.refresh_data()
1805
return self._resume_stream_with_vfs(response, src_format)
1806
if response[0][0] == 'missing-basis':
1807
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1808
resume_tokens = tokens
1809
return resume_tokens, set(missing_keys)
1811
self.target_repo.refresh_data()
1814
def _resume_stream_with_vfs(self, response, src_format):
1815
"""Resume sending a stream via VFS, first resending the record and
1816
substream that couldn't be sent via an insert_stream verb.
1818
if response[0][0] == 'missing-basis':
1819
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1820
# Ignore missing_keys, we haven't finished inserting yet
1823
def resume_substream():
1824
# Yield the substream that was interrupted.
1825
for record in self._last_substream:
1827
self._last_substream = None
1828
def resume_stream():
1829
# Finish sending the interrupted substream
1830
yield ('inventory-deltas', resume_substream())
1831
# Then simply continue sending the rest of the stream.
1832
for substream_kind, substream in self._last_stream:
1833
yield substream_kind, substream
1834
return self._insert_real(resume_stream(), src_format, tokens)
1836
def _stop_stream_if_inventory_delta(self, stream):
1837
"""Normally this just lets the original stream pass-through unchanged.
1839
However if any 'inventory-deltas' substream occurs it will stop
1840
streaming, and store the interrupted substream and stream in
1841
self._last_substream and self._last_stream so that the stream can be
1842
resumed by _resume_stream_with_vfs.
1845
stream_iter = iter(stream)
1846
for substream_kind, substream in stream_iter:
1847
if substream_kind == 'inventory-deltas':
1848
self._last_substream = substream
1849
self._last_stream = stream_iter
1852
yield substream_kind, substream
1855
class RemoteStreamSource(repository.StreamSource):
1856
"""Stream data from a remote server."""
1858
def get_stream(self, search):
1859
if (self.from_repository._fallback_repositories and
1860
self.to_format._fetch_order == 'topological'):
1861
return self._real_stream(self.from_repository, search)
1864
repos = [self.from_repository]
1870
repos.extend(repo._fallback_repositories)
1871
sources.append(repo)
1872
return self.missing_parents_chain(search, sources)
1874
def get_stream_for_missing_keys(self, missing_keys):
1875
self.from_repository._ensure_real()
1876
real_repo = self.from_repository._real_repository
1877
real_source = real_repo._get_source(self.to_format)
1878
return real_source.get_stream_for_missing_keys(missing_keys)
1880
def _real_stream(self, repo, search):
1881
"""Get a stream for search from repo.
1883
This never called RemoteStreamSource.get_stream, and is a heler
1884
for RemoteStreamSource._get_stream to allow getting a stream
1885
reliably whether fallback back because of old servers or trying
1886
to stream from a non-RemoteRepository (which the stacked support
1889
source = repo._get_source(self.to_format)
1890
if isinstance(source, RemoteStreamSource):
1892
source = repo._real_repository._get_source(self.to_format)
1893
return source.get_stream(search)
1895
def _get_stream(self, repo, search):
1896
"""Core worker to get a stream from repo for search.
1898
This is used by both get_stream and the stacking support logic. It
1899
deliberately gets a stream for repo which does not need to be
1900
self.from_repository. In the event that repo is not Remote, or
1901
cannot do a smart stream, a fallback is made to the generic
1902
repository._get_stream() interface, via self._real_stream.
1904
In the event of stacking, streams from _get_stream will not
1905
contain all the data for search - this is normal (see get_stream).
1907
:param repo: A repository.
1908
:param search: A search.
1910
# Fallbacks may be non-smart
1911
if not isinstance(repo, RemoteRepository):
1912
return self._real_stream(repo, search)
1913
client = repo._client
1914
medium = client._medium
1915
path = repo.bzrdir._path_for_remote_call(client)
1916
search_bytes = repo._serialise_search_result(search)
1917
args = (path, self.to_format.network_name())
1919
('Repository.get_stream_1.19', (1, 19)),
1920
('Repository.get_stream', (1, 13))]
1922
for verb, version in candidate_verbs:
1923
if medium._is_remote_before(version):
1926
response = repo._call_with_body_bytes_expecting_body(
1927
verb, args, search_bytes)
1928
except errors.UnknownSmartMethod:
1929
medium._remember_remote_is_before(version)
1931
response_tuple, response_handler = response
1935
return self._real_stream(repo, search)
1936
if response_tuple[0] != 'ok':
1937
raise errors.UnexpectedSmartServerResponse(response_tuple)
1938
byte_stream = response_handler.read_streamed_body()
1939
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1940
if src_format.network_name() != repo._format.network_name():
1941
raise AssertionError(
1942
"Mismatched RemoteRepository and stream src %r, %r" % (
1943
src_format.network_name(), repo._format.network_name()))
1946
def missing_parents_chain(self, search, sources):
1947
"""Chain multiple streams together to handle stacking.
1949
:param search: The overall search to satisfy with streams.
1950
:param sources: A list of Repository objects to query.
1952
self.from_serialiser = self.from_repository._format._serializer
1953
self.seen_revs = set()
1954
self.referenced_revs = set()
1955
# If there are heads in the search, or the key count is > 0, we are not
1957
while not search.is_empty() and len(sources) > 1:
1958
source = sources.pop(0)
1959
stream = self._get_stream(source, search)
1960
for kind, substream in stream:
1961
if kind != 'revisions':
1962
yield kind, substream
1964
yield kind, self.missing_parents_rev_handler(substream)
1965
search = search.refine(self.seen_revs, self.referenced_revs)
1966
self.seen_revs = set()
1967
self.referenced_revs = set()
1968
if not search.is_empty():
1969
for kind, stream in self._get_stream(sources[0], search):
1972
def missing_parents_rev_handler(self, substream):
1973
for content in substream:
1974
revision_bytes = content.get_bytes_as('fulltext')
1975
revision = self.from_serialiser.read_revision_from_string(
1977
self.seen_revs.add(content.key[-1])
1978
self.referenced_revs.update(revision.parent_ids)
1982
class RemoteBranchLockableFiles(LockableFiles):
1983
"""A 'LockableFiles' implementation that talks to a smart server.
1985
This is not a public interface class.
1988
def __init__(self, bzrdir, _client):
1989
self.bzrdir = bzrdir
1990
self._client = _client
1991
self._need_find_modes = True
1992
LockableFiles.__init__(
1993
self, bzrdir.get_branch_transport(None),
1994
'lock', lockdir.LockDir)
1996
def _find_modes(self):
1997
# RemoteBranches don't let the client set the mode of control files.
1998
self._dir_mode = None
1999
self._file_mode = None
2002
class RemoteBranchFormat(branch.BranchFormat):
2004
def __init__(self, network_name=None):
2005
super(RemoteBranchFormat, self).__init__()
2006
self._matchingbzrdir = RemoteBzrDirFormat()
2007
self._matchingbzrdir.set_branch_format(self)
2008
self._custom_format = None
2009
self._network_name = network_name
2011
def __eq__(self, other):
2012
return (isinstance(other, RemoteBranchFormat) and
2013
self.__dict__ == other.__dict__)
2015
def _ensure_real(self):
2016
if self._custom_format is None:
2017
self._custom_format = branch.network_format_registry.get(
2020
def get_format_description(self):
2022
return 'Remote: ' + self._custom_format.get_format_description()
2024
def network_name(self):
2025
return self._network_name
2027
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2028
return a_bzrdir.open_branch(name=name,
2029
ignore_fallbacks=ignore_fallbacks)
2031
def _vfs_initialize(self, a_bzrdir, name):
2032
# Initialisation when using a local bzrdir object, or a non-vfs init
2033
# method is not available on the server.
2034
# self._custom_format is always set - the start of initialize ensures
2036
if isinstance(a_bzrdir, RemoteBzrDir):
2037
a_bzrdir._ensure_real()
2038
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2041
# We assume the bzrdir is parameterised; it may not be.
2042
result = self._custom_format.initialize(a_bzrdir, name)
2043
if (isinstance(a_bzrdir, RemoteBzrDir) and
2044
not isinstance(result, RemoteBranch)):
2045
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2049
def initialize(self, a_bzrdir, name=None):
2050
# 1) get the network name to use.
2051
if self._custom_format:
2052
network_name = self._custom_format.network_name()
2054
# Select the current bzrlib default and ask for that.
2055
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2056
reference_format = reference_bzrdir_format.get_branch_format()
2057
self._custom_format = reference_format
2058
network_name = reference_format.network_name()
2059
# Being asked to create on a non RemoteBzrDir:
2060
if not isinstance(a_bzrdir, RemoteBzrDir):
2061
return self._vfs_initialize(a_bzrdir, name=name)
2062
medium = a_bzrdir._client._medium
2063
if medium._is_remote_before((1, 13)):
2064
return self._vfs_initialize(a_bzrdir, name=name)
2065
# Creating on a remote bzr dir.
2066
# 2) try direct creation via RPC
2067
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2068
if name is not None:
2069
raise errors.NoColocatedBranchSupport(self)
2070
verb = 'BzrDir.create_branch'
2072
response = a_bzrdir._call(verb, path, network_name)
2073
except errors.UnknownSmartMethod:
2074
# Fallback - use vfs methods
2075
medium._remember_remote_is_before((1, 13))
2076
return self._vfs_initialize(a_bzrdir, name=name)
2077
if response[0] != 'ok':
2078
raise errors.UnexpectedSmartServerResponse(response)
2079
# Turn the response into a RemoteRepository object.
2080
format = RemoteBranchFormat(network_name=response[1])
2081
repo_format = response_tuple_to_repo_format(response[3:])
2082
if response[2] == '':
2083
repo_bzrdir = a_bzrdir
2085
repo_bzrdir = RemoteBzrDir(
2086
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2088
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2089
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2090
format=format, setup_stacking=False, name=name)
2091
# XXX: We know this is a new branch, so it must have revno 0, revid
2092
# NULL_REVISION. Creating the branch locked would make this be unable
2093
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2094
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2095
return remote_branch
2097
def make_tags(self, branch):
2099
return self._custom_format.make_tags(branch)
2101
def supports_tags(self):
2102
# Remote branches might support tags, but we won't know until we
2103
# access the real remote branch.
2105
return self._custom_format.supports_tags()
2107
def supports_stacking(self):
2109
return self._custom_format.supports_stacking()
2111
def supports_set_append_revisions_only(self):
2113
return self._custom_format.supports_set_append_revisions_only()
2116
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2117
"""Branch stored on a server accessed by HPSS RPC.
2119
At the moment most operations are mapped down to simple file operations.
2122
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2123
_client=None, format=None, setup_stacking=True, name=None):
2124
"""Create a RemoteBranch instance.
2126
:param real_branch: An optional local implementation of the branch
2127
format, usually accessing the data via the VFS.
2128
:param _client: Private parameter for testing.
2129
:param format: A RemoteBranchFormat object, None to create one
2130
automatically. If supplied it should have a network_name already
2132
:param setup_stacking: If True make an RPC call to determine the
2133
stacked (or not) status of the branch. If False assume the branch
2135
:param name: Colocated branch name
2137
# We intentionally don't call the parent class's __init__, because it
2138
# will try to assign to self.tags, which is a property in this subclass.
2139
# And the parent's __init__ doesn't do much anyway.
2140
self.bzrdir = remote_bzrdir
2141
if _client is not None:
2142
self._client = _client
2144
self._client = remote_bzrdir._client
2145
self.repository = remote_repository
2146
if real_branch is not None:
2147
self._real_branch = real_branch
2148
# Give the remote repository the matching real repo.
2149
real_repo = self._real_branch.repository
2150
if isinstance(real_repo, RemoteRepository):
2151
real_repo._ensure_real()
2152
real_repo = real_repo._real_repository
2153
self.repository._set_real_repository(real_repo)
2154
# Give the branch the remote repository to let fast-pathing happen.
2155
self._real_branch.repository = self.repository
2157
self._real_branch = None
2158
# Fill out expected attributes of branch for bzrlib API users.
2159
self._clear_cached_state()
2160
self.base = self.bzrdir.root_transport.base
2162
self._control_files = None
2163
self._lock_mode = None
2164
self._lock_token = None
2165
self._repo_lock_token = None
2166
self._lock_count = 0
2167
self._leave_lock = False
2168
# Setup a format: note that we cannot call _ensure_real until all the
2169
# attributes above are set: This code cannot be moved higher up in this
2172
self._format = RemoteBranchFormat()
2173
if real_branch is not None:
2174
self._format._network_name = \
2175
self._real_branch._format.network_name()
2177
self._format = format
2178
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2179
# branch.open_branch method.
2180
self._real_ignore_fallbacks = not setup_stacking
2181
if not self._format._network_name:
2182
# Did not get from open_branchV2 - old server.
2184
self._format._network_name = \
2185
self._real_branch._format.network_name()
2186
self.tags = self._format.make_tags(self)
2187
# The base class init is not called, so we duplicate this:
2188
hooks = branch.Branch.hooks['open']
2191
self._is_stacked = False
2193
self._setup_stacking()
2195
def _setup_stacking(self):
2196
# configure stacking into the remote repository, by reading it from
2199
fallback_url = self.get_stacked_on_url()
2200
except (errors.NotStacked, errors.UnstackableBranchFormat,
2201
errors.UnstackableRepositoryFormat), e:
2203
self._is_stacked = True
2204
self._activate_fallback_location(fallback_url)
2206
def _get_config(self):
2207
return RemoteBranchConfig(self)
2209
def _get_real_transport(self):
2210
# if we try vfs access, return the real branch's vfs transport
2212
return self._real_branch._transport
2214
_transport = property(_get_real_transport)
2217
return "%s(%s)" % (self.__class__.__name__, self.base)
2221
def _ensure_real(self):
2222
"""Ensure that there is a _real_branch set.
2224
Used before calls to self._real_branch.
2226
if self._real_branch is None:
2227
if not vfs.vfs_enabled():
2228
raise AssertionError('smart server vfs must be enabled '
2229
'to use vfs implementation')
2230
self.bzrdir._ensure_real()
2231
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2232
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2233
if self.repository._real_repository is None:
2234
# Give the remote repository the matching real repo.
2235
real_repo = self._real_branch.repository
2236
if isinstance(real_repo, RemoteRepository):
2237
real_repo._ensure_real()
2238
real_repo = real_repo._real_repository
2239
self.repository._set_real_repository(real_repo)
2240
# Give the real branch the remote repository to let fast-pathing
2242
self._real_branch.repository = self.repository
2243
if self._lock_mode == 'r':
2244
self._real_branch.lock_read()
2245
elif self._lock_mode == 'w':
2246
self._real_branch.lock_write(token=self._lock_token)
2248
def _translate_error(self, err, **context):
2249
self.repository._translate_error(err, branch=self, **context)
2251
def _clear_cached_state(self):
2252
super(RemoteBranch, self)._clear_cached_state()
2253
if self._real_branch is not None:
2254
self._real_branch._clear_cached_state()
2256
def _clear_cached_state_of_remote_branch_only(self):
2257
"""Like _clear_cached_state, but doesn't clear the cache of
2260
This is useful when falling back to calling a method of
2261
self._real_branch that changes state. In that case the underlying
2262
branch changes, so we need to invalidate this RemoteBranch's cache of
2263
it. However, there's no need to invalidate the _real_branch's cache
2264
too, in fact doing so might harm performance.
2266
super(RemoteBranch, self)._clear_cached_state()
2269
def control_files(self):
2270
# Defer actually creating RemoteBranchLockableFiles until its needed,
2271
# because it triggers an _ensure_real that we otherwise might not need.
2272
if self._control_files is None:
2273
self._control_files = RemoteBranchLockableFiles(
2274
self.bzrdir, self._client)
2275
return self._control_files
2277
def _get_checkout_format(self):
2279
return self._real_branch._get_checkout_format()
2281
def get_physical_lock_status(self):
2282
"""See Branch.get_physical_lock_status()."""
2283
# should be an API call to the server, as branches must be lockable.
2285
return self._real_branch.get_physical_lock_status()
2287
def get_stacked_on_url(self):
2288
"""Get the URL this branch is stacked against.
2290
:raises NotStacked: If the branch is not stacked.
2291
:raises UnstackableBranchFormat: If the branch does not support
2293
:raises UnstackableRepositoryFormat: If the repository does not support
2297
# there may not be a repository yet, so we can't use
2298
# self._translate_error, so we can't use self._call either.
2299
response = self._client.call('Branch.get_stacked_on_url',
2300
self._remote_path())
2301
except errors.ErrorFromSmartServer, err:
2302
# there may not be a repository yet, so we can't call through
2303
# its _translate_error
2304
_translate_error(err, branch=self)
2305
except errors.UnknownSmartMethod, err:
2307
return self._real_branch.get_stacked_on_url()
2308
if response[0] != 'ok':
2309
raise errors.UnexpectedSmartServerResponse(response)
2312
def set_stacked_on_url(self, url):
2313
branch.Branch.set_stacked_on_url(self, url)
2315
self._is_stacked = False
2317
self._is_stacked = True
2319
def _vfs_get_tags_bytes(self):
2321
return self._real_branch._get_tags_bytes()
2323
def _get_tags_bytes(self):
2324
medium = self._client._medium
2325
if medium._is_remote_before((1, 13)):
2326
return self._vfs_get_tags_bytes()
2328
response = self._call('Branch.get_tags_bytes', self._remote_path())
2329
except errors.UnknownSmartMethod:
2330
medium._remember_remote_is_before((1, 13))
2331
return self._vfs_get_tags_bytes()
2334
def _vfs_set_tags_bytes(self, bytes):
2336
return self._real_branch._set_tags_bytes(bytes)
2338
def _set_tags_bytes(self, bytes):
2339
medium = self._client._medium
2340
if medium._is_remote_before((1, 18)):
2341
self._vfs_set_tags_bytes(bytes)
2345
self._remote_path(), self._lock_token, self._repo_lock_token)
2346
response = self._call_with_body_bytes(
2347
'Branch.set_tags_bytes', args, bytes)
2348
except errors.UnknownSmartMethod:
2349
medium._remember_remote_is_before((1, 18))
2350
self._vfs_set_tags_bytes(bytes)
2352
def lock_read(self):
2353
self.repository.lock_read()
2354
if not self._lock_mode:
2355
self._note_lock('r')
2356
self._lock_mode = 'r'
2357
self._lock_count = 1
2358
if self._real_branch is not None:
2359
self._real_branch.lock_read()
2361
self._lock_count += 1
2363
def _remote_lock_write(self, token):
2365
branch_token = repo_token = ''
2367
branch_token = token
2368
repo_token = self.repository.lock_write()
2369
self.repository.unlock()
2370
err_context = {'token': token}
2371
response = self._call(
2372
'Branch.lock_write', self._remote_path(), branch_token,
2373
repo_token or '', **err_context)
2374
if response[0] != 'ok':
2375
raise errors.UnexpectedSmartServerResponse(response)
2376
ok, branch_token, repo_token = response
2377
return branch_token, repo_token
2379
def lock_write(self, token=None):
2380
if not self._lock_mode:
2381
self._note_lock('w')
2382
# Lock the branch and repo in one remote call.
2383
remote_tokens = self._remote_lock_write(token)
2384
self._lock_token, self._repo_lock_token = remote_tokens
2385
if not self._lock_token:
2386
raise SmartProtocolError('Remote server did not return a token!')
2387
# Tell the self.repository object that it is locked.
2388
self.repository.lock_write(
2389
self._repo_lock_token, _skip_rpc=True)
2391
if self._real_branch is not None:
2392
self._real_branch.lock_write(token=self._lock_token)
2393
if token is not None:
2394
self._leave_lock = True
2396
self._leave_lock = False
2397
self._lock_mode = 'w'
2398
self._lock_count = 1
2399
elif self._lock_mode == 'r':
2400
raise errors.ReadOnlyTransaction
2402
if token is not None:
2403
# A token was given to lock_write, and we're relocking, so
2404
# check that the given token actually matches the one we
2406
if token != self._lock_token:
2407
raise errors.TokenMismatch(token, self._lock_token)
2408
self._lock_count += 1
2409
# Re-lock the repository too.
2410
self.repository.lock_write(self._repo_lock_token)
2411
return self._lock_token or None
2413
def _unlock(self, branch_token, repo_token):
2414
err_context = {'token': str((branch_token, repo_token))}
2415
response = self._call(
2416
'Branch.unlock', self._remote_path(), branch_token,
2417
repo_token or '', **err_context)
2418
if response == ('ok',):
2420
raise errors.UnexpectedSmartServerResponse(response)
2422
@only_raises(errors.LockNotHeld, errors.LockBroken)
2425
self._lock_count -= 1
2426
if not self._lock_count:
2427
self._clear_cached_state()
2428
mode = self._lock_mode
2429
self._lock_mode = None
2430
if self._real_branch is not None:
2431
if (not self._leave_lock and mode == 'w' and
2432
self._repo_lock_token):
2433
# If this RemoteBranch will remove the physical lock
2434
# for the repository, make sure the _real_branch
2435
# doesn't do it first. (Because the _real_branch's
2436
# repository is set to be the RemoteRepository.)
2437
self._real_branch.repository.leave_lock_in_place()
2438
self._real_branch.unlock()
2440
# Only write-locked branched need to make a remote method
2441
# call to perform the unlock.
2443
if not self._lock_token:
2444
raise AssertionError('Locked, but no token!')
2445
branch_token = self._lock_token
2446
repo_token = self._repo_lock_token
2447
self._lock_token = None
2448
self._repo_lock_token = None
2449
if not self._leave_lock:
2450
self._unlock(branch_token, repo_token)
2452
self.repository.unlock()
2454
def break_lock(self):
2456
return self._real_branch.break_lock()
2458
def leave_lock_in_place(self):
2459
if not self._lock_token:
2460
raise NotImplementedError(self.leave_lock_in_place)
2461
self._leave_lock = True
2463
def dont_leave_lock_in_place(self):
2464
if not self._lock_token:
2465
raise NotImplementedError(self.dont_leave_lock_in_place)
2466
self._leave_lock = False
2469
def get_rev_id(self, revno, history=None):
2471
return _mod_revision.NULL_REVISION
2472
last_revision_info = self.last_revision_info()
2473
ok, result = self.repository.get_rev_id_for_revno(
2474
revno, last_revision_info)
2477
missing_parent = result[1]
2478
# Either the revision named by the server is missing, or its parent
2479
# is. Call get_parent_map to determine which, so that we report a
2481
parent_map = self.repository.get_parent_map([missing_parent])
2482
if missing_parent in parent_map:
2483
missing_parent = parent_map[missing_parent]
2484
raise errors.RevisionNotPresent(missing_parent, self.repository)
2486
def _last_revision_info(self):
2487
response = self._call('Branch.last_revision_info', self._remote_path())
2488
if response[0] != 'ok':
2489
raise SmartProtocolError('unexpected response code %s' % (response,))
2490
revno = int(response[1])
2491
last_revision = response[2]
2492
return (revno, last_revision)
2494
def _gen_revision_history(self):
2495
"""See Branch._gen_revision_history()."""
2496
if self._is_stacked:
2498
return self._real_branch._gen_revision_history()
2499
response_tuple, response_handler = self._call_expecting_body(
2500
'Branch.revision_history', self._remote_path())
2501
if response_tuple[0] != 'ok':
2502
raise errors.UnexpectedSmartServerResponse(response_tuple)
2503
result = response_handler.read_body_bytes().split('\x00')
2508
def _remote_path(self):
2509
return self.bzrdir._path_for_remote_call(self._client)
2511
def _set_last_revision_descendant(self, revision_id, other_branch,
2512
allow_diverged=False, allow_overwrite_descendant=False):
2513
# This performs additional work to meet the hook contract; while its
2514
# undesirable, we have to synthesise the revno to call the hook, and
2515
# not calling the hook is worse as it means changes can't be prevented.
2516
# Having calculated this though, we can't just call into
2517
# set_last_revision_info as a simple call, because there is a set_rh
2518
# hook that some folk may still be using.
2519
old_revno, old_revid = self.last_revision_info()
2520
history = self._lefthand_history(revision_id)
2521
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2522
err_context = {'other_branch': other_branch}
2523
response = self._call('Branch.set_last_revision_ex',
2524
self._remote_path(), self._lock_token, self._repo_lock_token,
2525
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2527
self._clear_cached_state()
2528
if len(response) != 3 and response[0] != 'ok':
2529
raise errors.UnexpectedSmartServerResponse(response)
2530
new_revno, new_revision_id = response[1:]
2531
self._last_revision_info_cache = new_revno, new_revision_id
2532
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2533
if self._real_branch is not None:
2534
cache = new_revno, new_revision_id
2535
self._real_branch._last_revision_info_cache = cache
2537
def _set_last_revision(self, revision_id):
2538
old_revno, old_revid = self.last_revision_info()
2539
# This performs additional work to meet the hook contract; while its
2540
# undesirable, we have to synthesise the revno to call the hook, and
2541
# not calling the hook is worse as it means changes can't be prevented.
2542
# Having calculated this though, we can't just call into
2543
# set_last_revision_info as a simple call, because there is a set_rh
2544
# hook that some folk may still be using.
2545
history = self._lefthand_history(revision_id)
2546
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2547
self._clear_cached_state()
2548
response = self._call('Branch.set_last_revision',
2549
self._remote_path(), self._lock_token, self._repo_lock_token,
2551
if response != ('ok',):
2552
raise errors.UnexpectedSmartServerResponse(response)
2553
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2556
def set_revision_history(self, rev_history):
2557
# Send just the tip revision of the history; the server will generate
2558
# the full history from that. If the revision doesn't exist in this
2559
# branch, NoSuchRevision will be raised.
2560
if rev_history == []:
2563
rev_id = rev_history[-1]
2564
self._set_last_revision(rev_id)
2565
for hook in branch.Branch.hooks['set_rh']:
2566
hook(self, rev_history)
2567
self._cache_revision_history(rev_history)
2569
def _get_parent_location(self):
2570
medium = self._client._medium
2571
if medium._is_remote_before((1, 13)):
2572
return self._vfs_get_parent_location()
2574
response = self._call('Branch.get_parent', self._remote_path())
2575
except errors.UnknownSmartMethod:
2576
medium._remember_remote_is_before((1, 13))
2577
return self._vfs_get_parent_location()
2578
if len(response) != 1:
2579
raise errors.UnexpectedSmartServerResponse(response)
2580
parent_location = response[0]
2581
if parent_location == '':
2583
return parent_location
2585
def _vfs_get_parent_location(self):
2587
return self._real_branch._get_parent_location()
2589
def _set_parent_location(self, url):
2590
medium = self._client._medium
2591
if medium._is_remote_before((1, 15)):
2592
return self._vfs_set_parent_location(url)
2594
call_url = url or ''
2595
if type(call_url) is not str:
2596
raise AssertionError('url must be a str or None (%s)' % url)
2597
response = self._call('Branch.set_parent_location',
2598
self._remote_path(), self._lock_token, self._repo_lock_token,
2600
except errors.UnknownSmartMethod:
2601
medium._remember_remote_is_before((1, 15))
2602
return self._vfs_set_parent_location(url)
2604
raise errors.UnexpectedSmartServerResponse(response)
2606
def _vfs_set_parent_location(self, url):
2608
return self._real_branch._set_parent_location(url)
2611
def pull(self, source, overwrite=False, stop_revision=None,
2613
self._clear_cached_state_of_remote_branch_only()
2615
return self._real_branch.pull(
2616
source, overwrite=overwrite, stop_revision=stop_revision,
2617
_override_hook_target=self, **kwargs)
2620
def push(self, target, overwrite=False, stop_revision=None):
2622
return self._real_branch.push(
2623
target, overwrite=overwrite, stop_revision=stop_revision,
2624
_override_hook_source_branch=self)
2626
def is_locked(self):
2627
return self._lock_count >= 1
2630
def revision_id_to_revno(self, revision_id):
2632
return self._real_branch.revision_id_to_revno(revision_id)
2635
def set_last_revision_info(self, revno, revision_id):
2636
# XXX: These should be returned by the set_last_revision_info verb
2637
old_revno, old_revid = self.last_revision_info()
2638
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2639
revision_id = ensure_null(revision_id)
2641
response = self._call('Branch.set_last_revision_info',
2642
self._remote_path(), self._lock_token, self._repo_lock_token,
2643
str(revno), revision_id)
2644
except errors.UnknownSmartMethod:
2646
self._clear_cached_state_of_remote_branch_only()
2647
self._real_branch.set_last_revision_info(revno, revision_id)
2648
self._last_revision_info_cache = revno, revision_id
2650
if response == ('ok',):
2651
self._clear_cached_state()
2652
self._last_revision_info_cache = revno, revision_id
2653
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2654
# Update the _real_branch's cache too.
2655
if self._real_branch is not None:
2656
cache = self._last_revision_info_cache
2657
self._real_branch._last_revision_info_cache = cache
2659
raise errors.UnexpectedSmartServerResponse(response)
2662
def generate_revision_history(self, revision_id, last_rev=None,
2664
medium = self._client._medium
2665
if not medium._is_remote_before((1, 6)):
2666
# Use a smart method for 1.6 and above servers
2668
self._set_last_revision_descendant(revision_id, other_branch,
2669
allow_diverged=True, allow_overwrite_descendant=True)
2671
except errors.UnknownSmartMethod:
2672
medium._remember_remote_is_before((1, 6))
2673
self._clear_cached_state_of_remote_branch_only()
2674
self.set_revision_history(self._lefthand_history(revision_id,
2675
last_rev=last_rev,other_branch=other_branch))
2677
def set_push_location(self, location):
2679
return self._real_branch.set_push_location(location)
2682
class RemoteConfig(object):
2683
"""A Config that reads and writes from smart verbs.
2685
It is a low-level object that considers config data to be name/value pairs
2686
that may be associated with a section. Assigning meaning to the these
2687
values is done at higher levels like bzrlib.config.TreeConfig.
2690
def get_option(self, name, section=None, default=None):
2691
"""Return the value associated with a named option.
2693
:param name: The name of the value
2694
:param section: The section the option is in (if any)
2695
:param default: The value to return if the value is not set
2696
:return: The value or default value
2699
configobj = self._get_configobj()
2701
section_obj = configobj
2704
section_obj = configobj[section]
2707
return section_obj.get(name, default)
2708
except errors.UnknownSmartMethod:
2709
return self._vfs_get_option(name, section, default)
2711
def _response_to_configobj(self, response):
2712
if len(response[0]) and response[0][0] != 'ok':
2713
raise errors.UnexpectedSmartServerResponse(response)
2714
lines = response[1].read_body_bytes().splitlines()
2715
return config.ConfigObj(lines, encoding='utf-8')
2718
class RemoteBranchConfig(RemoteConfig):
2719
"""A RemoteConfig for Branches."""
2721
def __init__(self, branch):
2722
self._branch = branch
2724
def _get_configobj(self):
2725
path = self._branch._remote_path()
2726
response = self._branch._client.call_expecting_body(
2727
'Branch.get_config_file', path)
2728
return self._response_to_configobj(response)
2730
def set_option(self, value, name, section=None):
2731
"""Set the value associated with a named option.
2733
:param value: The value to set
2734
:param name: The name of the value to set
2735
:param section: The section the option is in (if any)
2737
medium = self._branch._client._medium
2738
if medium._is_remote_before((1, 14)):
2739
return self._vfs_set_option(value, name, section)
2741
path = self._branch._remote_path()
2742
response = self._branch._client.call('Branch.set_config_option',
2743
path, self._branch._lock_token, self._branch._repo_lock_token,
2744
value.encode('utf8'), name, section or '')
2745
except errors.UnknownSmartMethod:
2746
medium._remember_remote_is_before((1, 14))
2747
return self._vfs_set_option(value, name, section)
2749
raise errors.UnexpectedSmartServerResponse(response)
2751
def _real_object(self):
2752
self._branch._ensure_real()
2753
return self._branch._real_branch
2755
def _vfs_set_option(self, value, name, section=None):
2756
return self._real_object()._get_config().set_option(
2757
value, name, section)
2760
class RemoteBzrDirConfig(RemoteConfig):
2761
"""A RemoteConfig for BzrDirs."""
2763
def __init__(self, bzrdir):
2764
self._bzrdir = bzrdir
2766
def _get_configobj(self):
2767
medium = self._bzrdir._client._medium
2768
verb = 'BzrDir.get_config_file'
2769
if medium._is_remote_before((1, 15)):
2770
raise errors.UnknownSmartMethod(verb)
2771
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2772
response = self._bzrdir._call_expecting_body(
2774
return self._response_to_configobj(response)
2776
def _vfs_get_option(self, name, section, default):
2777
return self._real_object()._get_config().get_option(
2778
name, section, default)
2780
def set_option(self, value, name, section=None):
2781
"""Set the value associated with a named option.
2783
:param value: The value to set
2784
:param name: The name of the value to set
2785
:param section: The section the option is in (if any)
2787
return self._real_object()._get_config().set_option(
2788
value, name, section)
2790
def _real_object(self):
2791
self._bzrdir._ensure_real()
2792
return self._bzrdir._real_bzrdir
2796
def _extract_tar(tar, to_dir):
2797
"""Extract all the contents of a tarfile object.
2799
A replacement for extractall, which is not present in python2.4
2802
tar.extract(tarinfo, to_dir)
2805
def _translate_error(err, **context):
2806
"""Translate an ErrorFromSmartServer into a more useful error.
2808
Possible context keys:
2816
If the error from the server doesn't match a known pattern, then
2817
UnknownErrorFromSmartServer is raised.
2821
return context[name]
2822
except KeyError, key_err:
2823
mutter('Missing key %r in context %r', key_err.args[0], context)
2826
"""Get the path from the context if present, otherwise use first error
2830
return context['path']
2831
except KeyError, key_err:
2833
return err.error_args[0]
2834
except IndexError, idx_err:
2836
'Missing key %r in context %r', key_err.args[0], context)
2839
if err.error_verb == 'IncompatibleRepositories':
2840
raise errors.IncompatibleRepositories(err.error_args[0],
2841
err.error_args[1], err.error_args[2])
2842
elif err.error_verb == 'NoSuchRevision':
2843
raise NoSuchRevision(find('branch'), err.error_args[0])
2844
elif err.error_verb == 'nosuchrevision':
2845
raise NoSuchRevision(find('repository'), err.error_args[0])
2846
elif err.error_verb == 'nobranch':
2847
if len(err.error_args) >= 1:
2848
extra = err.error_args[0]
2851
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2853
elif err.error_verb == 'norepository':
2854
raise errors.NoRepositoryPresent(find('bzrdir'))
2855
elif err.error_verb == 'LockContention':
2856
raise errors.LockContention('(remote lock)')
2857
elif err.error_verb == 'UnlockableTransport':
2858
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2859
elif err.error_verb == 'LockFailed':
2860
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2861
elif err.error_verb == 'TokenMismatch':
2862
raise errors.TokenMismatch(find('token'), '(remote token)')
2863
elif err.error_verb == 'Diverged':
2864
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2865
elif err.error_verb == 'TipChangeRejected':
2866
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2867
elif err.error_verb == 'UnstackableBranchFormat':
2868
raise errors.UnstackableBranchFormat(*err.error_args)
2869
elif err.error_verb == 'UnstackableRepositoryFormat':
2870
raise errors.UnstackableRepositoryFormat(*err.error_args)
2871
elif err.error_verb == 'NotStacked':
2872
raise errors.NotStacked(branch=find('branch'))
2873
elif err.error_verb == 'PermissionDenied':
2875
if len(err.error_args) >= 2:
2876
extra = err.error_args[1]
2879
raise errors.PermissionDenied(path, extra=extra)
2880
elif err.error_verb == 'ReadError':
2882
raise errors.ReadError(path)
2883
elif err.error_verb == 'NoSuchFile':
2885
raise errors.NoSuchFile(path)
2886
elif err.error_verb == 'FileExists':
2887
raise errors.FileExists(err.error_args[0])
2888
elif err.error_verb == 'DirectoryNotEmpty':
2889
raise errors.DirectoryNotEmpty(err.error_args[0])
2890
elif err.error_verb == 'ShortReadvError':
2891
args = err.error_args
2892
raise errors.ShortReadvError(
2893
args[0], int(args[1]), int(args[2]), int(args[3]))
2894
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2895
encoding = str(err.error_args[0]) # encoding must always be a string
2896
val = err.error_args[1]
2897
start = int(err.error_args[2])
2898
end = int(err.error_args[3])
2899
reason = str(err.error_args[4]) # reason must always be a string
2900
if val.startswith('u:'):
2901
val = val[2:].decode('utf-8')
2902
elif val.startswith('s:'):
2903
val = val[2:].decode('base64')
2904
if err.error_verb == 'UnicodeDecodeError':
2905
raise UnicodeDecodeError(encoding, val, start, end, reason)
2906
elif err.error_verb == 'UnicodeEncodeError':
2907
raise UnicodeEncodeError(encoding, val, start, end, reason)
2908
elif err.error_verb == 'ReadOnlyError':
2909
raise errors.TransportNotPossible('readonly transport')
2910
raise errors.UnknownErrorFromSmartServer(err)