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
# XXX JRV20100304: Support creating colocated branches
2070
raise errors.NoColocatedBranchSupport(self)
2071
verb = 'BzrDir.create_branch'
2073
response = a_bzrdir._call(verb, path, network_name)
2074
except errors.UnknownSmartMethod:
2075
# Fallback - use vfs methods
2076
medium._remember_remote_is_before((1, 13))
2077
return self._vfs_initialize(a_bzrdir, name=name)
2078
if response[0] != 'ok':
2079
raise errors.UnexpectedSmartServerResponse(response)
2080
# Turn the response into a RemoteRepository object.
2081
format = RemoteBranchFormat(network_name=response[1])
2082
repo_format = response_tuple_to_repo_format(response[3:])
2083
if response[2] == '':
2084
repo_bzrdir = a_bzrdir
2086
repo_bzrdir = RemoteBzrDir(
2087
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2089
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2090
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2091
format=format, setup_stacking=False, name=name)
2092
# XXX: We know this is a new branch, so it must have revno 0, revid
2093
# NULL_REVISION. Creating the branch locked would make this be unable
2094
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2095
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2096
return remote_branch
2098
def make_tags(self, branch):
2100
return self._custom_format.make_tags(branch)
2102
def supports_tags(self):
2103
# Remote branches might support tags, but we won't know until we
2104
# access the real remote branch.
2106
return self._custom_format.supports_tags()
2108
def supports_stacking(self):
2110
return self._custom_format.supports_stacking()
2112
def supports_set_append_revisions_only(self):
2114
return self._custom_format.supports_set_append_revisions_only()
2117
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2118
"""Branch stored on a server accessed by HPSS RPC.
2120
At the moment most operations are mapped down to simple file operations.
2123
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2124
_client=None, format=None, setup_stacking=True, name=None):
2125
"""Create a RemoteBranch instance.
2127
:param real_branch: An optional local implementation of the branch
2128
format, usually accessing the data via the VFS.
2129
:param _client: Private parameter for testing.
2130
:param format: A RemoteBranchFormat object, None to create one
2131
automatically. If supplied it should have a network_name already
2133
:param setup_stacking: If True make an RPC call to determine the
2134
stacked (or not) status of the branch. If False assume the branch
2136
:param name: Colocated branch name
2138
# We intentionally don't call the parent class's __init__, because it
2139
# will try to assign to self.tags, which is a property in this subclass.
2140
# And the parent's __init__ doesn't do much anyway.
2141
self.bzrdir = remote_bzrdir
2142
if _client is not None:
2143
self._client = _client
2145
self._client = remote_bzrdir._client
2146
self.repository = remote_repository
2147
if real_branch is not None:
2148
self._real_branch = real_branch
2149
# Give the remote repository the matching real repo.
2150
real_repo = self._real_branch.repository
2151
if isinstance(real_repo, RemoteRepository):
2152
real_repo._ensure_real()
2153
real_repo = real_repo._real_repository
2154
self.repository._set_real_repository(real_repo)
2155
# Give the branch the remote repository to let fast-pathing happen.
2156
self._real_branch.repository = self.repository
2158
self._real_branch = None
2159
# Fill out expected attributes of branch for bzrlib API users.
2160
self._clear_cached_state()
2161
self.base = self.bzrdir.root_transport.base
2163
self._control_files = None
2164
self._lock_mode = None
2165
self._lock_token = None
2166
self._repo_lock_token = None
2167
self._lock_count = 0
2168
self._leave_lock = False
2169
# Setup a format: note that we cannot call _ensure_real until all the
2170
# attributes above are set: This code cannot be moved higher up in this
2173
self._format = RemoteBranchFormat()
2174
if real_branch is not None:
2175
self._format._network_name = \
2176
self._real_branch._format.network_name()
2178
self._format = format
2179
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2180
# branch.open_branch method.
2181
self._real_ignore_fallbacks = not setup_stacking
2182
if not self._format._network_name:
2183
# Did not get from open_branchV2 - old server.
2185
self._format._network_name = \
2186
self._real_branch._format.network_name()
2187
self.tags = self._format.make_tags(self)
2188
# The base class init is not called, so we duplicate this:
2189
hooks = branch.Branch.hooks['open']
2192
self._is_stacked = False
2194
self._setup_stacking()
2196
def _setup_stacking(self):
2197
# configure stacking into the remote repository, by reading it from
2200
fallback_url = self.get_stacked_on_url()
2201
except (errors.NotStacked, errors.UnstackableBranchFormat,
2202
errors.UnstackableRepositoryFormat), e:
2204
self._is_stacked = True
2205
self._activate_fallback_location(fallback_url)
2207
def _get_config(self):
2208
return RemoteBranchConfig(self)
2210
def _get_real_transport(self):
2211
# if we try vfs access, return the real branch's vfs transport
2213
return self._real_branch._transport
2215
_transport = property(_get_real_transport)
2218
return "%s(%s)" % (self.__class__.__name__, self.base)
2222
def _ensure_real(self):
2223
"""Ensure that there is a _real_branch set.
2225
Used before calls to self._real_branch.
2227
if self._real_branch is None:
2228
if not vfs.vfs_enabled():
2229
raise AssertionError('smart server vfs must be enabled '
2230
'to use vfs implementation')
2231
self.bzrdir._ensure_real()
2232
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2233
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2234
if self.repository._real_repository is None:
2235
# Give the remote repository the matching real repo.
2236
real_repo = self._real_branch.repository
2237
if isinstance(real_repo, RemoteRepository):
2238
real_repo._ensure_real()
2239
real_repo = real_repo._real_repository
2240
self.repository._set_real_repository(real_repo)
2241
# Give the real branch the remote repository to let fast-pathing
2243
self._real_branch.repository = self.repository
2244
if self._lock_mode == 'r':
2245
self._real_branch.lock_read()
2246
elif self._lock_mode == 'w':
2247
self._real_branch.lock_write(token=self._lock_token)
2249
def _translate_error(self, err, **context):
2250
self.repository._translate_error(err, branch=self, **context)
2252
def _clear_cached_state(self):
2253
super(RemoteBranch, self)._clear_cached_state()
2254
if self._real_branch is not None:
2255
self._real_branch._clear_cached_state()
2257
def _clear_cached_state_of_remote_branch_only(self):
2258
"""Like _clear_cached_state, but doesn't clear the cache of
2261
This is useful when falling back to calling a method of
2262
self._real_branch that changes state. In that case the underlying
2263
branch changes, so we need to invalidate this RemoteBranch's cache of
2264
it. However, there's no need to invalidate the _real_branch's cache
2265
too, in fact doing so might harm performance.
2267
super(RemoteBranch, self)._clear_cached_state()
2270
def control_files(self):
2271
# Defer actually creating RemoteBranchLockableFiles until its needed,
2272
# because it triggers an _ensure_real that we otherwise might not need.
2273
if self._control_files is None:
2274
self._control_files = RemoteBranchLockableFiles(
2275
self.bzrdir, self._client)
2276
return self._control_files
2278
def _get_checkout_format(self):
2280
return self._real_branch._get_checkout_format()
2282
def get_physical_lock_status(self):
2283
"""See Branch.get_physical_lock_status()."""
2284
# should be an API call to the server, as branches must be lockable.
2286
return self._real_branch.get_physical_lock_status()
2288
def get_stacked_on_url(self):
2289
"""Get the URL this branch is stacked against.
2291
:raises NotStacked: If the branch is not stacked.
2292
:raises UnstackableBranchFormat: If the branch does not support
2294
:raises UnstackableRepositoryFormat: If the repository does not support
2298
# there may not be a repository yet, so we can't use
2299
# self._translate_error, so we can't use self._call either.
2300
response = self._client.call('Branch.get_stacked_on_url',
2301
self._remote_path())
2302
except errors.ErrorFromSmartServer, err:
2303
# there may not be a repository yet, so we can't call through
2304
# its _translate_error
2305
_translate_error(err, branch=self)
2306
except errors.UnknownSmartMethod, err:
2308
return self._real_branch.get_stacked_on_url()
2309
if response[0] != 'ok':
2310
raise errors.UnexpectedSmartServerResponse(response)
2313
def set_stacked_on_url(self, url):
2314
branch.Branch.set_stacked_on_url(self, url)
2316
self._is_stacked = False
2318
self._is_stacked = True
2320
def _vfs_get_tags_bytes(self):
2322
return self._real_branch._get_tags_bytes()
2324
def _get_tags_bytes(self):
2325
medium = self._client._medium
2326
if medium._is_remote_before((1, 13)):
2327
return self._vfs_get_tags_bytes()
2329
response = self._call('Branch.get_tags_bytes', self._remote_path())
2330
except errors.UnknownSmartMethod:
2331
medium._remember_remote_is_before((1, 13))
2332
return self._vfs_get_tags_bytes()
2335
def _vfs_set_tags_bytes(self, bytes):
2337
return self._real_branch._set_tags_bytes(bytes)
2339
def _set_tags_bytes(self, bytes):
2340
medium = self._client._medium
2341
if medium._is_remote_before((1, 18)):
2342
self._vfs_set_tags_bytes(bytes)
2346
self._remote_path(), self._lock_token, self._repo_lock_token)
2347
response = self._call_with_body_bytes(
2348
'Branch.set_tags_bytes', args, bytes)
2349
except errors.UnknownSmartMethod:
2350
medium._remember_remote_is_before((1, 18))
2351
self._vfs_set_tags_bytes(bytes)
2353
def lock_read(self):
2354
self.repository.lock_read()
2355
if not self._lock_mode:
2356
self._note_lock('r')
2357
self._lock_mode = 'r'
2358
self._lock_count = 1
2359
if self._real_branch is not None:
2360
self._real_branch.lock_read()
2362
self._lock_count += 1
2364
def _remote_lock_write(self, token):
2366
branch_token = repo_token = ''
2368
branch_token = token
2369
repo_token = self.repository.lock_write()
2370
self.repository.unlock()
2371
err_context = {'token': token}
2372
response = self._call(
2373
'Branch.lock_write', self._remote_path(), branch_token,
2374
repo_token or '', **err_context)
2375
if response[0] != 'ok':
2376
raise errors.UnexpectedSmartServerResponse(response)
2377
ok, branch_token, repo_token = response
2378
return branch_token, repo_token
2380
def lock_write(self, token=None):
2381
if not self._lock_mode:
2382
self._note_lock('w')
2383
# Lock the branch and repo in one remote call.
2384
remote_tokens = self._remote_lock_write(token)
2385
self._lock_token, self._repo_lock_token = remote_tokens
2386
if not self._lock_token:
2387
raise SmartProtocolError('Remote server did not return a token!')
2388
# Tell the self.repository object that it is locked.
2389
self.repository.lock_write(
2390
self._repo_lock_token, _skip_rpc=True)
2392
if self._real_branch is not None:
2393
self._real_branch.lock_write(token=self._lock_token)
2394
if token is not None:
2395
self._leave_lock = True
2397
self._leave_lock = False
2398
self._lock_mode = 'w'
2399
self._lock_count = 1
2400
elif self._lock_mode == 'r':
2401
raise errors.ReadOnlyTransaction
2403
if token is not None:
2404
# A token was given to lock_write, and we're relocking, so
2405
# check that the given token actually matches the one we
2407
if token != self._lock_token:
2408
raise errors.TokenMismatch(token, self._lock_token)
2409
self._lock_count += 1
2410
# Re-lock the repository too.
2411
self.repository.lock_write(self._repo_lock_token)
2412
return self._lock_token or None
2414
def _unlock(self, branch_token, repo_token):
2415
err_context = {'token': str((branch_token, repo_token))}
2416
response = self._call(
2417
'Branch.unlock', self._remote_path(), branch_token,
2418
repo_token or '', **err_context)
2419
if response == ('ok',):
2421
raise errors.UnexpectedSmartServerResponse(response)
2423
@only_raises(errors.LockNotHeld, errors.LockBroken)
2426
self._lock_count -= 1
2427
if not self._lock_count:
2428
self._clear_cached_state()
2429
mode = self._lock_mode
2430
self._lock_mode = None
2431
if self._real_branch is not None:
2432
if (not self._leave_lock and mode == 'w' and
2433
self._repo_lock_token):
2434
# If this RemoteBranch will remove the physical lock
2435
# for the repository, make sure the _real_branch
2436
# doesn't do it first. (Because the _real_branch's
2437
# repository is set to be the RemoteRepository.)
2438
self._real_branch.repository.leave_lock_in_place()
2439
self._real_branch.unlock()
2441
# Only write-locked branched need to make a remote method
2442
# call to perform the unlock.
2444
if not self._lock_token:
2445
raise AssertionError('Locked, but no token!')
2446
branch_token = self._lock_token
2447
repo_token = self._repo_lock_token
2448
self._lock_token = None
2449
self._repo_lock_token = None
2450
if not self._leave_lock:
2451
self._unlock(branch_token, repo_token)
2453
self.repository.unlock()
2455
def break_lock(self):
2457
return self._real_branch.break_lock()
2459
def leave_lock_in_place(self):
2460
if not self._lock_token:
2461
raise NotImplementedError(self.leave_lock_in_place)
2462
self._leave_lock = True
2464
def dont_leave_lock_in_place(self):
2465
if not self._lock_token:
2466
raise NotImplementedError(self.dont_leave_lock_in_place)
2467
self._leave_lock = False
2470
def get_rev_id(self, revno, history=None):
2472
return _mod_revision.NULL_REVISION
2473
last_revision_info = self.last_revision_info()
2474
ok, result = self.repository.get_rev_id_for_revno(
2475
revno, last_revision_info)
2478
missing_parent = result[1]
2479
# Either the revision named by the server is missing, or its parent
2480
# is. Call get_parent_map to determine which, so that we report a
2482
parent_map = self.repository.get_parent_map([missing_parent])
2483
if missing_parent in parent_map:
2484
missing_parent = parent_map[missing_parent]
2485
raise errors.RevisionNotPresent(missing_parent, self.repository)
2487
def _last_revision_info(self):
2488
response = self._call('Branch.last_revision_info', self._remote_path())
2489
if response[0] != 'ok':
2490
raise SmartProtocolError('unexpected response code %s' % (response,))
2491
revno = int(response[1])
2492
last_revision = response[2]
2493
return (revno, last_revision)
2495
def _gen_revision_history(self):
2496
"""See Branch._gen_revision_history()."""
2497
if self._is_stacked:
2499
return self._real_branch._gen_revision_history()
2500
response_tuple, response_handler = self._call_expecting_body(
2501
'Branch.revision_history', self._remote_path())
2502
if response_tuple[0] != 'ok':
2503
raise errors.UnexpectedSmartServerResponse(response_tuple)
2504
result = response_handler.read_body_bytes().split('\x00')
2509
def _remote_path(self):
2510
return self.bzrdir._path_for_remote_call(self._client)
2512
def _set_last_revision_descendant(self, revision_id, other_branch,
2513
allow_diverged=False, allow_overwrite_descendant=False):
2514
# This performs additional work to meet the hook contract; while its
2515
# undesirable, we have to synthesise the revno to call the hook, and
2516
# not calling the hook is worse as it means changes can't be prevented.
2517
# Having calculated this though, we can't just call into
2518
# set_last_revision_info as a simple call, because there is a set_rh
2519
# hook that some folk may still be using.
2520
old_revno, old_revid = self.last_revision_info()
2521
history = self._lefthand_history(revision_id)
2522
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2523
err_context = {'other_branch': other_branch}
2524
response = self._call('Branch.set_last_revision_ex',
2525
self._remote_path(), self._lock_token, self._repo_lock_token,
2526
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2528
self._clear_cached_state()
2529
if len(response) != 3 and response[0] != 'ok':
2530
raise errors.UnexpectedSmartServerResponse(response)
2531
new_revno, new_revision_id = response[1:]
2532
self._last_revision_info_cache = new_revno, new_revision_id
2533
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2534
if self._real_branch is not None:
2535
cache = new_revno, new_revision_id
2536
self._real_branch._last_revision_info_cache = cache
2538
def _set_last_revision(self, revision_id):
2539
old_revno, old_revid = self.last_revision_info()
2540
# This performs additional work to meet the hook contract; while its
2541
# undesirable, we have to synthesise the revno to call the hook, and
2542
# not calling the hook is worse as it means changes can't be prevented.
2543
# Having calculated this though, we can't just call into
2544
# set_last_revision_info as a simple call, because there is a set_rh
2545
# hook that some folk may still be using.
2546
history = self._lefthand_history(revision_id)
2547
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2548
self._clear_cached_state()
2549
response = self._call('Branch.set_last_revision',
2550
self._remote_path(), self._lock_token, self._repo_lock_token,
2552
if response != ('ok',):
2553
raise errors.UnexpectedSmartServerResponse(response)
2554
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2557
def set_revision_history(self, rev_history):
2558
# Send just the tip revision of the history; the server will generate
2559
# the full history from that. If the revision doesn't exist in this
2560
# branch, NoSuchRevision will be raised.
2561
if rev_history == []:
2564
rev_id = rev_history[-1]
2565
self._set_last_revision(rev_id)
2566
for hook in branch.Branch.hooks['set_rh']:
2567
hook(self, rev_history)
2568
self._cache_revision_history(rev_history)
2570
def _get_parent_location(self):
2571
medium = self._client._medium
2572
if medium._is_remote_before((1, 13)):
2573
return self._vfs_get_parent_location()
2575
response = self._call('Branch.get_parent', self._remote_path())
2576
except errors.UnknownSmartMethod:
2577
medium._remember_remote_is_before((1, 13))
2578
return self._vfs_get_parent_location()
2579
if len(response) != 1:
2580
raise errors.UnexpectedSmartServerResponse(response)
2581
parent_location = response[0]
2582
if parent_location == '':
2584
return parent_location
2586
def _vfs_get_parent_location(self):
2588
return self._real_branch._get_parent_location()
2590
def _set_parent_location(self, url):
2591
medium = self._client._medium
2592
if medium._is_remote_before((1, 15)):
2593
return self._vfs_set_parent_location(url)
2595
call_url = url or ''
2596
if type(call_url) is not str:
2597
raise AssertionError('url must be a str or None (%s)' % url)
2598
response = self._call('Branch.set_parent_location',
2599
self._remote_path(), self._lock_token, self._repo_lock_token,
2601
except errors.UnknownSmartMethod:
2602
medium._remember_remote_is_before((1, 15))
2603
return self._vfs_set_parent_location(url)
2605
raise errors.UnexpectedSmartServerResponse(response)
2607
def _vfs_set_parent_location(self, url):
2609
return self._real_branch._set_parent_location(url)
2612
def pull(self, source, overwrite=False, stop_revision=None,
2614
self._clear_cached_state_of_remote_branch_only()
2616
return self._real_branch.pull(
2617
source, overwrite=overwrite, stop_revision=stop_revision,
2618
_override_hook_target=self, **kwargs)
2621
def push(self, target, overwrite=False, stop_revision=None):
2623
return self._real_branch.push(
2624
target, overwrite=overwrite, stop_revision=stop_revision,
2625
_override_hook_source_branch=self)
2627
def is_locked(self):
2628
return self._lock_count >= 1
2631
def revision_id_to_revno(self, revision_id):
2633
return self._real_branch.revision_id_to_revno(revision_id)
2636
def set_last_revision_info(self, revno, revision_id):
2637
# XXX: These should be returned by the set_last_revision_info verb
2638
old_revno, old_revid = self.last_revision_info()
2639
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2640
revision_id = ensure_null(revision_id)
2642
response = self._call('Branch.set_last_revision_info',
2643
self._remote_path(), self._lock_token, self._repo_lock_token,
2644
str(revno), revision_id)
2645
except errors.UnknownSmartMethod:
2647
self._clear_cached_state_of_remote_branch_only()
2648
self._real_branch.set_last_revision_info(revno, revision_id)
2649
self._last_revision_info_cache = revno, revision_id
2651
if response == ('ok',):
2652
self._clear_cached_state()
2653
self._last_revision_info_cache = revno, revision_id
2654
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2655
# Update the _real_branch's cache too.
2656
if self._real_branch is not None:
2657
cache = self._last_revision_info_cache
2658
self._real_branch._last_revision_info_cache = cache
2660
raise errors.UnexpectedSmartServerResponse(response)
2663
def generate_revision_history(self, revision_id, last_rev=None,
2665
medium = self._client._medium
2666
if not medium._is_remote_before((1, 6)):
2667
# Use a smart method for 1.6 and above servers
2669
self._set_last_revision_descendant(revision_id, other_branch,
2670
allow_diverged=True, allow_overwrite_descendant=True)
2672
except errors.UnknownSmartMethod:
2673
medium._remember_remote_is_before((1, 6))
2674
self._clear_cached_state_of_remote_branch_only()
2675
self.set_revision_history(self._lefthand_history(revision_id,
2676
last_rev=last_rev,other_branch=other_branch))
2678
def set_push_location(self, location):
2680
return self._real_branch.set_push_location(location)
2683
class RemoteConfig(object):
2684
"""A Config that reads and writes from smart verbs.
2686
It is a low-level object that considers config data to be name/value pairs
2687
that may be associated with a section. Assigning meaning to the these
2688
values is done at higher levels like bzrlib.config.TreeConfig.
2691
def get_option(self, name, section=None, default=None):
2692
"""Return the value associated with a named option.
2694
:param name: The name of the value
2695
:param section: The section the option is in (if any)
2696
:param default: The value to return if the value is not set
2697
:return: The value or default value
2700
configobj = self._get_configobj()
2702
section_obj = configobj
2705
section_obj = configobj[section]
2708
return section_obj.get(name, default)
2709
except errors.UnknownSmartMethod:
2710
return self._vfs_get_option(name, section, default)
2712
def _response_to_configobj(self, response):
2713
if len(response[0]) and response[0][0] != 'ok':
2714
raise errors.UnexpectedSmartServerResponse(response)
2715
lines = response[1].read_body_bytes().splitlines()
2716
return config.ConfigObj(lines, encoding='utf-8')
2719
class RemoteBranchConfig(RemoteConfig):
2720
"""A RemoteConfig for Branches."""
2722
def __init__(self, branch):
2723
self._branch = branch
2725
def _get_configobj(self):
2726
path = self._branch._remote_path()
2727
response = self._branch._client.call_expecting_body(
2728
'Branch.get_config_file', path)
2729
return self._response_to_configobj(response)
2731
def set_option(self, value, name, section=None):
2732
"""Set the value associated with a named option.
2734
:param value: The value to set
2735
:param name: The name of the value to set
2736
:param section: The section the option is in (if any)
2738
medium = self._branch._client._medium
2739
if medium._is_remote_before((1, 14)):
2740
return self._vfs_set_option(value, name, section)
2742
path = self._branch._remote_path()
2743
response = self._branch._client.call('Branch.set_config_option',
2744
path, self._branch._lock_token, self._branch._repo_lock_token,
2745
value.encode('utf8'), name, section or '')
2746
except errors.UnknownSmartMethod:
2747
medium._remember_remote_is_before((1, 14))
2748
return self._vfs_set_option(value, name, section)
2750
raise errors.UnexpectedSmartServerResponse(response)
2752
def _real_object(self):
2753
self._branch._ensure_real()
2754
return self._branch._real_branch
2756
def _vfs_set_option(self, value, name, section=None):
2757
return self._real_object()._get_config().set_option(
2758
value, name, section)
2761
class RemoteBzrDirConfig(RemoteConfig):
2762
"""A RemoteConfig for BzrDirs."""
2764
def __init__(self, bzrdir):
2765
self._bzrdir = bzrdir
2767
def _get_configobj(self):
2768
medium = self._bzrdir._client._medium
2769
verb = 'BzrDir.get_config_file'
2770
if medium._is_remote_before((1, 15)):
2771
raise errors.UnknownSmartMethod(verb)
2772
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2773
response = self._bzrdir._call_expecting_body(
2775
return self._response_to_configobj(response)
2777
def _vfs_get_option(self, name, section, default):
2778
return self._real_object()._get_config().get_option(
2779
name, section, default)
2781
def set_option(self, value, name, section=None):
2782
"""Set the value associated with a named option.
2784
:param value: The value to set
2785
:param name: The name of the value to set
2786
:param section: The section the option is in (if any)
2788
return self._real_object()._get_config().set_option(
2789
value, name, section)
2791
def _real_object(self):
2792
self._bzrdir._ensure_real()
2793
return self._bzrdir._real_bzrdir
2797
def _extract_tar(tar, to_dir):
2798
"""Extract all the contents of a tarfile object.
2800
A replacement for extractall, which is not present in python2.4
2803
tar.extract(tarinfo, to_dir)
2806
def _translate_error(err, **context):
2807
"""Translate an ErrorFromSmartServer into a more useful error.
2809
Possible context keys:
2817
If the error from the server doesn't match a known pattern, then
2818
UnknownErrorFromSmartServer is raised.
2822
return context[name]
2823
except KeyError, key_err:
2824
mutter('Missing key %r in context %r', key_err.args[0], context)
2827
"""Get the path from the context if present, otherwise use first error
2831
return context['path']
2832
except KeyError, key_err:
2834
return err.error_args[0]
2835
except IndexError, idx_err:
2837
'Missing key %r in context %r', key_err.args[0], context)
2840
if err.error_verb == 'IncompatibleRepositories':
2841
raise errors.IncompatibleRepositories(err.error_args[0],
2842
err.error_args[1], err.error_args[2])
2843
elif err.error_verb == 'NoSuchRevision':
2844
raise NoSuchRevision(find('branch'), err.error_args[0])
2845
elif err.error_verb == 'nosuchrevision':
2846
raise NoSuchRevision(find('repository'), err.error_args[0])
2847
elif err.error_verb == 'nobranch':
2848
if len(err.error_args) >= 1:
2849
extra = err.error_args[0]
2852
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2854
elif err.error_verb == 'norepository':
2855
raise errors.NoRepositoryPresent(find('bzrdir'))
2856
elif err.error_verb == 'LockContention':
2857
raise errors.LockContention('(remote lock)')
2858
elif err.error_verb == 'UnlockableTransport':
2859
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2860
elif err.error_verb == 'LockFailed':
2861
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2862
elif err.error_verb == 'TokenMismatch':
2863
raise errors.TokenMismatch(find('token'), '(remote token)')
2864
elif err.error_verb == 'Diverged':
2865
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2866
elif err.error_verb == 'TipChangeRejected':
2867
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2868
elif err.error_verb == 'UnstackableBranchFormat':
2869
raise errors.UnstackableBranchFormat(*err.error_args)
2870
elif err.error_verb == 'UnstackableRepositoryFormat':
2871
raise errors.UnstackableRepositoryFormat(*err.error_args)
2872
elif err.error_verb == 'NotStacked':
2873
raise errors.NotStacked(branch=find('branch'))
2874
elif err.error_verb == 'PermissionDenied':
2876
if len(err.error_args) >= 2:
2877
extra = err.error_args[1]
2880
raise errors.PermissionDenied(path, extra=extra)
2881
elif err.error_verb == 'ReadError':
2883
raise errors.ReadError(path)
2884
elif err.error_verb == 'NoSuchFile':
2886
raise errors.NoSuchFile(path)
2887
elif err.error_verb == 'FileExists':
2888
raise errors.FileExists(err.error_args[0])
2889
elif err.error_verb == 'DirectoryNotEmpty':
2890
raise errors.DirectoryNotEmpty(err.error_args[0])
2891
elif err.error_verb == 'ShortReadvError':
2892
args = err.error_args
2893
raise errors.ShortReadvError(
2894
args[0], int(args[1]), int(args[2]), int(args[3]))
2895
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2896
encoding = str(err.error_args[0]) # encoding must always be a string
2897
val = err.error_args[1]
2898
start = int(err.error_args[2])
2899
end = int(err.error_args[3])
2900
reason = str(err.error_args[4]) # reason must always be a string
2901
if val.startswith('u:'):
2902
val = val[2:].decode('utf-8')
2903
elif val.startswith('s:'):
2904
val = val[2:].decode('base64')
2905
if err.error_verb == 'UnicodeDecodeError':
2906
raise UnicodeDecodeError(encoding, val, start, end, reason)
2907
elif err.error_verb == 'UnicodeEncodeError':
2908
raise UnicodeEncodeError(encoding, val, start, end, reason)
2909
elif err.error_verb == 'ReadOnlyError':
2910
raise errors.TransportNotPossible('readonly transport')
2911
raise errors.UnknownErrorFromSmartServer(err)