1
# Copyright (C) 2006, 2007, 2008, 2009 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):
246
# as per meta1 formats - just delegate to the format object which may
248
real_branch = self._format.get_branch_format().initialize(self)
249
if not isinstance(real_branch, RemoteBranch):
250
result = RemoteBranch(self, self.find_repository(), real_branch)
253
# BzrDir.clone_on_transport() uses the result of create_branch but does
254
# not return it to its callers; we save approximately 8% of our round
255
# trips by handing the branch we created back to the first caller to
256
# open_branch rather than probing anew. Long term we need a API in
257
# bzrdir that doesn't discard result objects (like result_branch).
259
self._next_open_branch_result = result
262
def destroy_branch(self):
263
"""See BzrDir.destroy_branch"""
265
self._real_bzrdir.destroy_branch()
266
self._next_open_branch_result = None
268
def create_workingtree(self, revision_id=None, from_branch=None):
269
raise errors.NotLocalUrl(self.transport.base)
271
def find_branch_format(self):
272
"""Find the branch 'format' for this bzrdir.
274
This might be a synthetic object for e.g. RemoteBranch and SVN.
276
b = self.open_branch()
279
def get_branch_reference(self):
280
"""See BzrDir.get_branch_reference()."""
281
response = self._get_branch_reference()
282
if response[0] == 'ref':
287
def _get_branch_reference(self):
288
path = self._path_for_remote_call(self._client)
289
medium = self._client._medium
291
('BzrDir.open_branchV3', (2, 1)),
292
('BzrDir.open_branchV2', (1, 13)),
293
('BzrDir.open_branch', None),
295
for verb, required_version in candidate_calls:
296
if required_version and medium._is_remote_before(required_version):
299
response = self._call(verb, path)
300
except errors.UnknownSmartMethod:
301
if required_version is None:
303
medium._remember_remote_is_before(required_version)
306
if verb == 'BzrDir.open_branch':
307
if response[0] != 'ok':
308
raise errors.UnexpectedSmartServerResponse(response)
309
if response[1] != '':
310
return ('ref', response[1])
312
return ('branch', '')
313
if response[0] not in ('ref', 'branch'):
314
raise errors.UnexpectedSmartServerResponse(response)
317
def _get_tree_branch(self):
318
"""See BzrDir._get_tree_branch()."""
319
return None, self.open_branch()
321
def open_branch(self, _unsupported=False, ignore_fallbacks=False):
323
raise NotImplementedError('unsupported flag support not implemented yet.')
324
if self._next_open_branch_result is not None:
325
# See create_branch for details.
326
result = self._next_open_branch_result
327
self._next_open_branch_result = None
329
response = self._get_branch_reference()
330
if response[0] == 'ref':
331
# a branch reference, use the existing BranchReference logic.
332
format = BranchReferenceFormat()
333
return format.open(self, _found=True, location=response[1],
334
ignore_fallbacks=ignore_fallbacks)
335
branch_format_name = response[1]
336
if not branch_format_name:
337
branch_format_name = None
338
format = RemoteBranchFormat(network_name=branch_format_name)
339
return RemoteBranch(self, self.find_repository(), format=format,
340
setup_stacking=not ignore_fallbacks)
342
def _open_repo_v1(self, path):
343
verb = 'BzrDir.find_repository'
344
response = self._call(verb, path)
345
if response[0] != 'ok':
346
raise errors.UnexpectedSmartServerResponse(response)
347
# servers that only support the v1 method don't support external
350
repo = self._real_bzrdir.open_repository()
351
response = response + ('no', repo._format.network_name())
352
return response, repo
354
def _open_repo_v2(self, path):
355
verb = 'BzrDir.find_repositoryV2'
356
response = self._call(verb, path)
357
if response[0] != 'ok':
358
raise errors.UnexpectedSmartServerResponse(response)
360
repo = self._real_bzrdir.open_repository()
361
response = response + (repo._format.network_name(),)
362
return response, repo
364
def _open_repo_v3(self, path):
365
verb = 'BzrDir.find_repositoryV3'
366
medium = self._client._medium
367
if medium._is_remote_before((1, 13)):
368
raise errors.UnknownSmartMethod(verb)
370
response = self._call(verb, path)
371
except errors.UnknownSmartMethod:
372
medium._remember_remote_is_before((1, 13))
374
if response[0] != 'ok':
375
raise errors.UnexpectedSmartServerResponse(response)
376
return response, None
378
def open_repository(self):
379
path = self._path_for_remote_call(self._client)
381
for probe in [self._open_repo_v3, self._open_repo_v2,
384
response, real_repo = probe(path)
386
except errors.UnknownSmartMethod:
389
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
390
if response[0] != 'ok':
391
raise errors.UnexpectedSmartServerResponse(response)
392
if len(response) != 6:
393
raise SmartProtocolError('incorrect response length %s' % (response,))
394
if response[1] == '':
395
# repo is at this dir.
396
format = response_tuple_to_repo_format(response[2:])
397
# Used to support creating a real format instance when needed.
398
format._creating_bzrdir = self
399
remote_repo = RemoteRepository(self, format)
400
format._creating_repo = remote_repo
401
if real_repo is not None:
402
remote_repo._set_real_repository(real_repo)
405
raise errors.NoRepositoryPresent(self)
407
def has_workingtree(self):
408
if self._has_working_tree is None:
410
self._has_working_tree = self._real_bzrdir.has_workingtree()
411
return self._has_working_tree
413
def open_workingtree(self, recommend_upgrade=True):
414
if self.has_workingtree():
415
raise errors.NotLocalUrl(self.root_transport)
417
raise errors.NoWorkingTree(self.root_transport.base)
419
def _path_for_remote_call(self, client):
420
"""Return the path to be used for this bzrdir in a remote call."""
421
return client.remote_path_from_transport(self.root_transport)
423
def get_branch_transport(self, branch_format):
425
return self._real_bzrdir.get_branch_transport(branch_format)
427
def get_repository_transport(self, repository_format):
429
return self._real_bzrdir.get_repository_transport(repository_format)
431
def get_workingtree_transport(self, workingtree_format):
433
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
435
def can_convert_format(self):
436
"""Upgrading of remote bzrdirs is not supported yet."""
439
def needs_format_conversion(self, format=None):
440
"""Upgrading of remote bzrdirs is not supported yet."""
442
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
443
% 'needs_format_conversion(format=None)')
446
def clone(self, url, revision_id=None, force_new_repo=False,
447
preserve_stacking=False):
449
return self._real_bzrdir.clone(url, revision_id=revision_id,
450
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
452
def _get_config(self):
453
return RemoteBzrDirConfig(self)
456
class RemoteRepositoryFormat(repository.RepositoryFormat):
457
"""Format for repositories accessed over a _SmartClient.
459
Instances of this repository are represented by RemoteRepository
462
The RemoteRepositoryFormat is parameterized during construction
463
to reflect the capabilities of the real, remote format. Specifically
464
the attributes rich_root_data and supports_tree_reference are set
465
on a per instance basis, and are not set (and should not be) at
468
:ivar _custom_format: If set, a specific concrete repository format that
469
will be used when initializing a repository with this
470
RemoteRepositoryFormat.
471
:ivar _creating_repo: If set, the repository object that this
472
RemoteRepositoryFormat was created for: it can be called into
473
to obtain data like the network name.
476
_matchingbzrdir = RemoteBzrDirFormat()
479
repository.RepositoryFormat.__init__(self)
480
self._custom_format = None
481
self._network_name = None
482
self._creating_bzrdir = None
483
self._supports_chks = None
484
self._supports_external_lookups = None
485
self._supports_tree_reference = None
486
self._rich_root_data = None
489
return "%s(_network_name=%r)" % (self.__class__.__name__,
493
def fast_deltas(self):
495
return self._custom_format.fast_deltas
498
def rich_root_data(self):
499
if self._rich_root_data is None:
501
self._rich_root_data = self._custom_format.rich_root_data
502
return self._rich_root_data
505
def supports_chks(self):
506
if self._supports_chks is None:
508
self._supports_chks = self._custom_format.supports_chks
509
return self._supports_chks
512
def supports_external_lookups(self):
513
if self._supports_external_lookups is None:
515
self._supports_external_lookups = \
516
self._custom_format.supports_external_lookups
517
return self._supports_external_lookups
520
def supports_tree_reference(self):
521
if self._supports_tree_reference is None:
523
self._supports_tree_reference = \
524
self._custom_format.supports_tree_reference
525
return self._supports_tree_reference
527
def _vfs_initialize(self, a_bzrdir, shared):
528
"""Helper for common code in initialize."""
529
if self._custom_format:
530
# Custom format requested
531
result = self._custom_format.initialize(a_bzrdir, shared=shared)
532
elif self._creating_bzrdir is not None:
533
# Use the format that the repository we were created to back
535
prior_repo = self._creating_bzrdir.open_repository()
536
prior_repo._ensure_real()
537
result = prior_repo._real_repository._format.initialize(
538
a_bzrdir, shared=shared)
540
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
541
# support remote initialization.
542
# We delegate to a real object at this point (as RemoteBzrDir
543
# delegate to the repository format which would lead to infinite
544
# recursion if we just called a_bzrdir.create_repository.
545
a_bzrdir._ensure_real()
546
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
547
if not isinstance(result, RemoteRepository):
548
return self.open(a_bzrdir)
552
def initialize(self, a_bzrdir, shared=False):
553
# Being asked to create on a non RemoteBzrDir:
554
if not isinstance(a_bzrdir, RemoteBzrDir):
555
return self._vfs_initialize(a_bzrdir, shared)
556
medium = a_bzrdir._client._medium
557
if medium._is_remote_before((1, 13)):
558
return self._vfs_initialize(a_bzrdir, shared)
559
# Creating on a remote bzr dir.
560
# 1) get the network name to use.
561
if self._custom_format:
562
network_name = self._custom_format.network_name()
563
elif self._network_name:
564
network_name = self._network_name
566
# Select the current bzrlib default and ask for that.
567
reference_bzrdir_format = bzrdir.format_registry.get('default')()
568
reference_format = reference_bzrdir_format.repository_format
569
network_name = reference_format.network_name()
570
# 2) try direct creation via RPC
571
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
572
verb = 'BzrDir.create_repository'
578
response = a_bzrdir._call(verb, path, network_name, shared_str)
579
except errors.UnknownSmartMethod:
580
# Fallback - use vfs methods
581
medium._remember_remote_is_before((1, 13))
582
return self._vfs_initialize(a_bzrdir, shared)
584
# Turn the response into a RemoteRepository object.
585
format = response_tuple_to_repo_format(response[1:])
586
# Used to support creating a real format instance when needed.
587
format._creating_bzrdir = a_bzrdir
588
remote_repo = RemoteRepository(a_bzrdir, format)
589
format._creating_repo = remote_repo
592
def open(self, a_bzrdir):
593
if not isinstance(a_bzrdir, RemoteBzrDir):
594
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
595
return a_bzrdir.open_repository()
597
def _ensure_real(self):
598
if self._custom_format is None:
599
self._custom_format = repository.network_format_registry.get(
603
def _fetch_order(self):
605
return self._custom_format._fetch_order
608
def _fetch_uses_deltas(self):
610
return self._custom_format._fetch_uses_deltas
613
def _fetch_reconcile(self):
615
return self._custom_format._fetch_reconcile
617
def get_format_description(self):
619
return 'Remote: ' + self._custom_format.get_format_description()
621
def __eq__(self, other):
622
return self.__class__ is other.__class__
624
def network_name(self):
625
if self._network_name:
626
return self._network_name
627
self._creating_repo._ensure_real()
628
return self._creating_repo._real_repository._format.network_name()
631
def pack_compresses(self):
633
return self._custom_format.pack_compresses
636
def _serializer(self):
638
return self._custom_format._serializer
641
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
642
"""Repository accessed over rpc.
644
For the moment most operations are performed using local transport-backed
648
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
649
"""Create a RemoteRepository instance.
651
:param remote_bzrdir: The bzrdir hosting this repository.
652
:param format: The RemoteFormat object to use.
653
:param real_repository: If not None, a local implementation of the
654
repository logic for the repository, usually accessing the data
656
:param _client: Private testing parameter - override the smart client
657
to be used by the repository.
660
self._real_repository = real_repository
662
self._real_repository = None
663
self.bzrdir = remote_bzrdir
665
self._client = remote_bzrdir._client
667
self._client = _client
668
self._format = format
669
self._lock_mode = None
670
self._lock_token = None
672
self._leave_lock = False
673
# Cache of revision parents; misses are cached during read locks, and
674
# write locks when no _real_repository has been set.
675
self._unstacked_provider = graph.CachingParentsProvider(
676
get_parent_map=self._get_parent_map_rpc)
677
self._unstacked_provider.disable_cache()
679
# These depend on the actual remote format, so force them off for
680
# maximum compatibility. XXX: In future these should depend on the
681
# remote repository instance, but this is irrelevant until we perform
682
# reconcile via an RPC call.
683
self._reconcile_does_inventory_gc = False
684
self._reconcile_fixes_text_parents = False
685
self._reconcile_backsup_inventory = False
686
self.base = self.bzrdir.transport.base
687
# Additional places to query for data.
688
self._fallback_repositories = []
691
return "%s(%s)" % (self.__class__.__name__, self.base)
695
def abort_write_group(self, suppress_errors=False):
696
"""Complete a write group on the decorated repository.
698
Smart methods perform operations in a single step so this API
699
is not really applicable except as a compatibility thunk
700
for older plugins that don't use e.g. the CommitBuilder
703
:param suppress_errors: see Repository.abort_write_group.
706
return self._real_repository.abort_write_group(
707
suppress_errors=suppress_errors)
711
"""Decorate the real repository for now.
713
In the long term a full blown network facility is needed to avoid
714
creating a real repository object locally.
717
return self._real_repository.chk_bytes
719
def commit_write_group(self):
720
"""Complete a write group on the decorated repository.
722
Smart methods perform operations in a single step so this API
723
is not really applicable except as a compatibility thunk
724
for older plugins that don't use e.g. the CommitBuilder
728
return self._real_repository.commit_write_group()
730
def resume_write_group(self, tokens):
732
return self._real_repository.resume_write_group(tokens)
734
def suspend_write_group(self):
736
return self._real_repository.suspend_write_group()
738
def get_missing_parent_inventories(self, check_for_missing_texts=True):
740
return self._real_repository.get_missing_parent_inventories(
741
check_for_missing_texts=check_for_missing_texts)
743
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
745
return self._real_repository.get_rev_id_for_revno(
748
def get_rev_id_for_revno(self, revno, known_pair):
749
"""See Repository.get_rev_id_for_revno."""
750
path = self.bzrdir._path_for_remote_call(self._client)
752
if self._client._medium._is_remote_before((1, 17)):
753
return self._get_rev_id_for_revno_vfs(revno, known_pair)
754
response = self._call(
755
'Repository.get_rev_id_for_revno', path, revno, known_pair)
756
except errors.UnknownSmartMethod:
757
self._client._medium._remember_remote_is_before((1, 17))
758
return self._get_rev_id_for_revno_vfs(revno, known_pair)
759
if response[0] == 'ok':
760
return True, response[1]
761
elif response[0] == 'history-incomplete':
762
known_pair = response[1:3]
763
for fallback in self._fallback_repositories:
764
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
769
# Not found in any fallbacks
770
return False, known_pair
772
raise errors.UnexpectedSmartServerResponse(response)
774
def _ensure_real(self):
775
"""Ensure that there is a _real_repository set.
777
Used before calls to self._real_repository.
779
Note that _ensure_real causes many roundtrips to the server which are
780
not desirable, and prevents the use of smart one-roundtrip RPC's to
781
perform complex operations (such as accessing parent data, streaming
782
revisions etc). Adding calls to _ensure_real should only be done when
783
bringing up new functionality, adding fallbacks for smart methods that
784
require a fallback path, and never to replace an existing smart method
785
invocation. If in doubt chat to the bzr network team.
787
if self._real_repository is None:
788
if 'hpssvfs' in debug.debug_flags:
790
warning('VFS Repository access triggered\n%s',
791
''.join(traceback.format_stack()))
792
self._unstacked_provider.missing_keys.clear()
793
self.bzrdir._ensure_real()
794
self._set_real_repository(
795
self.bzrdir._real_bzrdir.open_repository())
797
def _translate_error(self, err, **context):
798
self.bzrdir._translate_error(err, repository=self, **context)
800
def find_text_key_references(self):
801
"""Find the text key references within the repository.
803
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
804
revision_ids. Each altered file-ids has the exact revision_ids that
805
altered it listed explicitly.
806
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
807
to whether they were referred to by the inventory of the
808
revision_id that they contain. The inventory texts from all present
809
revision ids are assessed to generate this report.
812
return self._real_repository.find_text_key_references()
814
def _generate_text_key_index(self):
815
"""Generate a new text key index for the repository.
817
This is an expensive function that will take considerable time to run.
819
:return: A dict mapping (file_id, revision_id) tuples to a list of
820
parents, also (file_id, revision_id) tuples.
823
return self._real_repository._generate_text_key_index()
825
def _get_revision_graph(self, revision_id):
826
"""Private method for using with old (< 1.2) servers to fallback."""
827
if revision_id is None:
829
elif revision.is_null(revision_id):
832
path = self.bzrdir._path_for_remote_call(self._client)
833
response = self._call_expecting_body(
834
'Repository.get_revision_graph', path, revision_id)
835
response_tuple, response_handler = response
836
if response_tuple[0] != 'ok':
837
raise errors.UnexpectedSmartServerResponse(response_tuple)
838
coded = response_handler.read_body_bytes()
840
# no revisions in this repository!
842
lines = coded.split('\n')
845
d = tuple(line.split())
846
revision_graph[d[0]] = d[1:]
848
return revision_graph
851
"""See Repository._get_sink()."""
852
return RemoteStreamSink(self)
854
def _get_source(self, to_format):
855
"""Return a source for streaming from this repository."""
856
return RemoteStreamSource(self, to_format)
859
def has_revision(self, revision_id):
860
"""True if this repository has a copy of the revision."""
861
# Copy of bzrlib.repository.Repository.has_revision
862
return revision_id in self.has_revisions((revision_id,))
865
def has_revisions(self, revision_ids):
866
"""Probe to find out the presence of multiple revisions.
868
:param revision_ids: An iterable of revision_ids.
869
:return: A set of the revision_ids that were present.
871
# Copy of bzrlib.repository.Repository.has_revisions
872
parent_map = self.get_parent_map(revision_ids)
873
result = set(parent_map)
874
if _mod_revision.NULL_REVISION in revision_ids:
875
result.add(_mod_revision.NULL_REVISION)
878
def _has_same_fallbacks(self, other_repo):
879
"""Returns true if the repositories have the same fallbacks."""
880
# XXX: copied from Repository; it should be unified into a base class
881
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
882
my_fb = self._fallback_repositories
883
other_fb = other_repo._fallback_repositories
884
if len(my_fb) != len(other_fb):
886
for f, g in zip(my_fb, other_fb):
887
if not f.has_same_location(g):
891
def has_same_location(self, other):
892
# TODO: Move to RepositoryBase and unify with the regular Repository
893
# one; unfortunately the tests rely on slightly different behaviour at
894
# present -- mbp 20090710
895
return (self.__class__ is other.__class__ and
896
self.bzrdir.transport.base == other.bzrdir.transport.base)
898
def get_graph(self, other_repository=None):
899
"""Return the graph for this repository format"""
900
parents_provider = self._make_parents_provider(other_repository)
901
return graph.Graph(parents_provider)
903
def gather_stats(self, revid=None, committers=None):
904
"""See Repository.gather_stats()."""
905
path = self.bzrdir._path_for_remote_call(self._client)
906
# revid can be None to indicate no revisions, not just NULL_REVISION
907
if revid is None or revision.is_null(revid):
911
if committers is None or not committers:
912
fmt_committers = 'no'
914
fmt_committers = 'yes'
915
response_tuple, response_handler = self._call_expecting_body(
916
'Repository.gather_stats', path, fmt_revid, fmt_committers)
917
if response_tuple[0] != 'ok':
918
raise errors.UnexpectedSmartServerResponse(response_tuple)
920
body = response_handler.read_body_bytes()
922
for line in body.split('\n'):
925
key, val_text = line.split(':')
926
if key in ('revisions', 'size', 'committers'):
927
result[key] = int(val_text)
928
elif key in ('firstrev', 'latestrev'):
929
values = val_text.split(' ')[1:]
930
result[key] = (float(values[0]), long(values[1]))
934
def find_branches(self, using=False):
935
"""See Repository.find_branches()."""
936
# should be an API call to the server.
938
return self._real_repository.find_branches(using=using)
940
def get_physical_lock_status(self):
941
"""See Repository.get_physical_lock_status()."""
942
# should be an API call to the server.
944
return self._real_repository.get_physical_lock_status()
946
def is_in_write_group(self):
947
"""Return True if there is an open write group.
949
write groups are only applicable locally for the smart server..
951
if self._real_repository:
952
return self._real_repository.is_in_write_group()
955
return self._lock_count >= 1
958
"""See Repository.is_shared()."""
959
path = self.bzrdir._path_for_remote_call(self._client)
960
response = self._call('Repository.is_shared', path)
961
if response[0] not in ('yes', 'no'):
962
raise SmartProtocolError('unexpected response code %s' % (response,))
963
return response[0] == 'yes'
965
def is_write_locked(self):
966
return self._lock_mode == 'w'
968
def _warn_if_deprecated(self, branch=None):
969
# If we have a real repository, the check will be done there, if we
970
# don't the check will be done remotely.
974
# wrong eventually - want a local lock cache context
975
if not self._lock_mode:
977
self._lock_mode = 'r'
979
self._unstacked_provider.enable_cache(cache_misses=True)
980
if self._real_repository is not None:
981
self._real_repository.lock_read()
982
for repo in self._fallback_repositories:
985
self._lock_count += 1
987
def _remote_lock_write(self, token):
988
path = self.bzrdir._path_for_remote_call(self._client)
991
err_context = {'token': token}
992
response = self._call('Repository.lock_write', path, token,
994
if response[0] == 'ok':
998
raise errors.UnexpectedSmartServerResponse(response)
1000
def lock_write(self, token=None, _skip_rpc=False):
1001
if not self._lock_mode:
1002
self._note_lock('w')
1004
if self._lock_token is not None:
1005
if token != self._lock_token:
1006
raise errors.TokenMismatch(token, self._lock_token)
1007
self._lock_token = token
1009
self._lock_token = self._remote_lock_write(token)
1010
# if self._lock_token is None, then this is something like packs or
1011
# svn where we don't get to lock the repo, or a weave style repository
1012
# where we cannot lock it over the wire and attempts to do so will
1014
if self._real_repository is not None:
1015
self._real_repository.lock_write(token=self._lock_token)
1016
if token is not None:
1017
self._leave_lock = True
1019
self._leave_lock = False
1020
self._lock_mode = 'w'
1021
self._lock_count = 1
1022
cache_misses = self._real_repository is None
1023
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1024
for repo in self._fallback_repositories:
1025
# Writes don't affect fallback repos
1027
elif self._lock_mode == 'r':
1028
raise errors.ReadOnlyError(self)
1030
self._lock_count += 1
1031
return self._lock_token or None
1033
def leave_lock_in_place(self):
1034
if not self._lock_token:
1035
raise NotImplementedError(self.leave_lock_in_place)
1036
self._leave_lock = True
1038
def dont_leave_lock_in_place(self):
1039
if not self._lock_token:
1040
raise NotImplementedError(self.dont_leave_lock_in_place)
1041
self._leave_lock = False
1043
def _set_real_repository(self, repository):
1044
"""Set the _real_repository for this repository.
1046
:param repository: The repository to fallback to for non-hpss
1047
implemented operations.
1049
if self._real_repository is not None:
1050
# Replacing an already set real repository.
1051
# We cannot do this [currently] if the repository is locked -
1052
# synchronised state might be lost.
1053
if self.is_locked():
1054
raise AssertionError('_real_repository is already set')
1055
if isinstance(repository, RemoteRepository):
1056
raise AssertionError()
1057
self._real_repository = repository
1058
# three code paths happen here:
1059
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1060
# up stacking. In this case self._fallback_repositories is [], and the
1061
# real repo is already setup. Preserve the real repo and
1062
# RemoteRepository.add_fallback_repository will avoid adding
1064
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1065
# ensure_real is triggered from a branch, the real repository to
1066
# set already has a matching list with separate instances, but
1067
# as they are also RemoteRepositories we don't worry about making the
1068
# lists be identical.
1069
# 3) new servers, RemoteRepository.ensure_real is triggered before
1070
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1071
# and need to populate it.
1072
if (self._fallback_repositories and
1073
len(self._real_repository._fallback_repositories) !=
1074
len(self._fallback_repositories)):
1075
if len(self._real_repository._fallback_repositories):
1076
raise AssertionError(
1077
"cannot cleanly remove existing _fallback_repositories")
1078
for fb in self._fallback_repositories:
1079
self._real_repository.add_fallback_repository(fb)
1080
if self._lock_mode == 'w':
1081
# if we are already locked, the real repository must be able to
1082
# acquire the lock with our token.
1083
self._real_repository.lock_write(self._lock_token)
1084
elif self._lock_mode == 'r':
1085
self._real_repository.lock_read()
1087
def start_write_group(self):
1088
"""Start a write group on the decorated repository.
1090
Smart methods perform operations in a single step so this API
1091
is not really applicable except as a compatibility thunk
1092
for older plugins that don't use e.g. the CommitBuilder
1096
return self._real_repository.start_write_group()
1098
def _unlock(self, token):
1099
path = self.bzrdir._path_for_remote_call(self._client)
1101
# with no token the remote repository is not persistently locked.
1103
err_context = {'token': token}
1104
response = self._call('Repository.unlock', path, token,
1106
if response == ('ok',):
1109
raise errors.UnexpectedSmartServerResponse(response)
1111
@only_raises(errors.LockNotHeld, errors.LockBroken)
1113
if not self._lock_count:
1114
return lock.cant_unlock_not_held(self)
1115
self._lock_count -= 1
1116
if self._lock_count > 0:
1118
self._unstacked_provider.disable_cache()
1119
old_mode = self._lock_mode
1120
self._lock_mode = None
1122
# The real repository is responsible at present for raising an
1123
# exception if it's in an unfinished write group. However, it
1124
# normally will *not* actually remove the lock from disk - that's
1125
# done by the server on receiving the Repository.unlock call.
1126
# This is just to let the _real_repository stay up to date.
1127
if self._real_repository is not None:
1128
self._real_repository.unlock()
1130
# The rpc-level lock should be released even if there was a
1131
# problem releasing the vfs-based lock.
1133
# Only write-locked repositories need to make a remote method
1134
# call to perform the unlock.
1135
old_token = self._lock_token
1136
self._lock_token = None
1137
if not self._leave_lock:
1138
self._unlock(old_token)
1139
# Fallbacks are always 'lock_read()' so we don't pay attention to
1141
for repo in self._fallback_repositories:
1144
def break_lock(self):
1145
# should hand off to the network
1147
return self._real_repository.break_lock()
1149
def _get_tarball(self, compression):
1150
"""Return a TemporaryFile containing a repository tarball.
1152
Returns None if the server does not support sending tarballs.
1155
path = self.bzrdir._path_for_remote_call(self._client)
1157
response, protocol = self._call_expecting_body(
1158
'Repository.tarball', path, compression)
1159
except errors.UnknownSmartMethod:
1160
protocol.cancel_read_body()
1162
if response[0] == 'ok':
1163
# Extract the tarball and return it
1164
t = tempfile.NamedTemporaryFile()
1165
# TODO: rpc layer should read directly into it...
1166
t.write(protocol.read_body_bytes())
1169
raise errors.UnexpectedSmartServerResponse(response)
1171
def sprout(self, to_bzrdir, revision_id=None):
1172
# TODO: Option to control what format is created?
1174
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1176
dest_repo.fetch(self, revision_id=revision_id)
1179
### These methods are just thin shims to the VFS object for now.
1181
def revision_tree(self, revision_id):
1183
return self._real_repository.revision_tree(revision_id)
1185
def get_serializer_format(self):
1187
return self._real_repository.get_serializer_format()
1189
def get_commit_builder(self, branch, parents, config, timestamp=None,
1190
timezone=None, committer=None, revprops=None,
1192
# FIXME: It ought to be possible to call this without immediately
1193
# triggering _ensure_real. For now it's the easiest thing to do.
1195
real_repo = self._real_repository
1196
builder = real_repo.get_commit_builder(branch, parents,
1197
config, timestamp=timestamp, timezone=timezone,
1198
committer=committer, revprops=revprops, revision_id=revision_id)
1201
def add_fallback_repository(self, repository):
1202
"""Add a repository to use for looking up data not held locally.
1204
:param repository: A repository.
1206
if not self._format.supports_external_lookups:
1207
raise errors.UnstackableRepositoryFormat(
1208
self._format.network_name(), self.base)
1209
# We need to accumulate additional repositories here, to pass them in
1212
if self.is_locked():
1213
# We will call fallback.unlock() when we transition to the unlocked
1214
# state, so always add a lock here. If a caller passes us a locked
1215
# repository, they are responsible for unlocking it later.
1216
repository.lock_read()
1217
self._fallback_repositories.append(repository)
1218
# If self._real_repository was parameterised already (e.g. because a
1219
# _real_branch had its get_stacked_on_url method called), then the
1220
# repository to be added may already be in the _real_repositories list.
1221
if self._real_repository is not None:
1222
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1223
self._real_repository._fallback_repositories]
1224
if repository.bzrdir.root_transport.base not in fallback_locations:
1225
self._real_repository.add_fallback_repository(repository)
1227
def add_inventory(self, revid, inv, parents):
1229
return self._real_repository.add_inventory(revid, inv, parents)
1231
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1234
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1235
delta, new_revision_id, parents)
1237
def add_revision(self, rev_id, rev, inv=None, config=None):
1239
return self._real_repository.add_revision(
1240
rev_id, rev, inv=inv, config=config)
1243
def get_inventory(self, revision_id):
1245
return self._real_repository.get_inventory(revision_id)
1247
def iter_inventories(self, revision_ids, ordering=None):
1249
return self._real_repository.iter_inventories(revision_ids, ordering)
1252
def get_revision(self, revision_id):
1254
return self._real_repository.get_revision(revision_id)
1256
def get_transaction(self):
1258
return self._real_repository.get_transaction()
1261
def clone(self, a_bzrdir, revision_id=None):
1263
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1265
def make_working_trees(self):
1266
"""See Repository.make_working_trees"""
1268
return self._real_repository.make_working_trees()
1270
def refresh_data(self):
1271
"""Re-read any data needed to to synchronise with disk.
1273
This method is intended to be called after another repository instance
1274
(such as one used by a smart server) has inserted data into the
1275
repository. It may not be called during a write group, but may be
1276
called at any other time.
1278
if self.is_in_write_group():
1279
raise errors.InternalBzrError(
1280
"May not refresh_data while in a write group.")
1281
if self._real_repository is not None:
1282
self._real_repository.refresh_data()
1284
def revision_ids_to_search_result(self, result_set):
1285
"""Convert a set of revision ids to a graph SearchResult."""
1286
result_parents = set()
1287
for parents in self.get_graph().get_parent_map(
1288
result_set).itervalues():
1289
result_parents.update(parents)
1290
included_keys = result_set.intersection(result_parents)
1291
start_keys = result_set.difference(included_keys)
1292
exclude_keys = result_parents.difference(result_set)
1293
result = graph.SearchResult(start_keys, exclude_keys,
1294
len(result_set), result_set)
1298
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1299
"""Return the revision ids that other has that this does not.
1301
These are returned in topological order.
1303
revision_id: only return revision ids included by revision_id.
1305
return repository.InterRepository.get(
1306
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1308
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1310
# No base implementation to use as RemoteRepository is not a subclass
1311
# of Repository; so this is a copy of Repository.fetch().
1312
if fetch_spec is not None and revision_id is not None:
1313
raise AssertionError(
1314
"fetch_spec and revision_id are mutually exclusive.")
1315
if self.is_in_write_group():
1316
raise errors.InternalBzrError(
1317
"May not fetch while in a write group.")
1318
# fast path same-url fetch operations
1319
if (self.has_same_location(source)
1320
and fetch_spec is None
1321
and self._has_same_fallbacks(source)):
1322
# check that last_revision is in 'from' and then return a
1324
if (revision_id is not None and
1325
not revision.is_null(revision_id)):
1326
self.get_revision(revision_id)
1328
# if there is no specific appropriate InterRepository, this will get
1329
# the InterRepository base class, which raises an
1330
# IncompatibleRepositories when asked to fetch.
1331
inter = repository.InterRepository.get(source, self)
1332
return inter.fetch(revision_id=revision_id, pb=pb,
1333
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1335
def create_bundle(self, target, base, fileobj, format=None):
1337
self._real_repository.create_bundle(target, base, fileobj, format)
1340
def get_ancestry(self, revision_id, topo_sorted=True):
1342
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1344
def fileids_altered_by_revision_ids(self, revision_ids):
1346
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1348
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1350
return self._real_repository._get_versioned_file_checker(
1351
revisions, revision_versions_cache)
1353
def iter_files_bytes(self, desired_files):
1354
"""See Repository.iter_file_bytes.
1357
return self._real_repository.iter_files_bytes(desired_files)
1359
def get_parent_map(self, revision_ids):
1360
"""See bzrlib.Graph.get_parent_map()."""
1361
return self._make_parents_provider().get_parent_map(revision_ids)
1363
def _get_parent_map_rpc(self, keys):
1364
"""Helper for get_parent_map that performs the RPC."""
1365
medium = self._client._medium
1366
if medium._is_remote_before((1, 2)):
1367
# We already found out that the server can't understand
1368
# Repository.get_parent_map requests, so just fetch the whole
1371
# Note that this reads the whole graph, when only some keys are
1372
# wanted. On this old server there's no way (?) to get them all
1373
# in one go, and the user probably will have seen a warning about
1374
# the server being old anyhow.
1375
rg = self._get_revision_graph(None)
1376
# There is an API discrepancy between get_parent_map and
1377
# get_revision_graph. Specifically, a "key:()" pair in
1378
# get_revision_graph just means a node has no parents. For
1379
# "get_parent_map" it means the node is a ghost. So fix up the
1380
# graph to correct this.
1381
# https://bugs.launchpad.net/bzr/+bug/214894
1382
# There is one other "bug" which is that ghosts in
1383
# get_revision_graph() are not returned at all. But we won't worry
1384
# about that for now.
1385
for node_id, parent_ids in rg.iteritems():
1386
if parent_ids == ():
1387
rg[node_id] = (NULL_REVISION,)
1388
rg[NULL_REVISION] = ()
1393
raise ValueError('get_parent_map(None) is not valid')
1394
if NULL_REVISION in keys:
1395
keys.discard(NULL_REVISION)
1396
found_parents = {NULL_REVISION:()}
1398
return found_parents
1401
# TODO(Needs analysis): We could assume that the keys being requested
1402
# from get_parent_map are in a breadth first search, so typically they
1403
# will all be depth N from some common parent, and we don't have to
1404
# have the server iterate from the root parent, but rather from the
1405
# keys we're searching; and just tell the server the keyspace we
1406
# already have; but this may be more traffic again.
1408
# Transform self._parents_map into a search request recipe.
1409
# TODO: Manage this incrementally to avoid covering the same path
1410
# repeatedly. (The server will have to on each request, but the less
1411
# work done the better).
1413
# Negative caching notes:
1414
# new server sends missing when a request including the revid
1415
# 'include-missing:' is present in the request.
1416
# missing keys are serialised as missing:X, and we then call
1417
# provider.note_missing(X) for-all X
1418
parents_map = self._unstacked_provider.get_cached_map()
1419
if parents_map is None:
1420
# Repository is not locked, so there's no cache.
1422
# start_set is all the keys in the cache
1423
start_set = set(parents_map)
1424
# result set is all the references to keys in the cache
1425
result_parents = set()
1426
for parents in parents_map.itervalues():
1427
result_parents.update(parents)
1428
stop_keys = result_parents.difference(start_set)
1429
# We don't need to send ghosts back to the server as a position to
1431
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1432
key_count = len(parents_map)
1433
if (NULL_REVISION in result_parents
1434
and NULL_REVISION in self._unstacked_provider.missing_keys):
1435
# If we pruned NULL_REVISION from the stop_keys because it's also
1436
# in our cache of "missing" keys we need to increment our key count
1437
# by 1, because the reconsitituted SearchResult on the server will
1438
# still consider NULL_REVISION to be an included key.
1440
included_keys = start_set.intersection(result_parents)
1441
start_set.difference_update(included_keys)
1442
recipe = ('manual', start_set, stop_keys, key_count)
1443
body = self._serialise_search_recipe(recipe)
1444
path = self.bzrdir._path_for_remote_call(self._client)
1446
if type(key) is not str:
1448
"key %r not a plain string" % (key,))
1449
verb = 'Repository.get_parent_map'
1450
args = (path, 'include-missing:') + tuple(keys)
1452
response = self._call_with_body_bytes_expecting_body(
1454
except errors.UnknownSmartMethod:
1455
# Server does not support this method, so get the whole graph.
1456
# Worse, we have to force a disconnection, because the server now
1457
# doesn't realise it has a body on the wire to consume, so the
1458
# only way to recover is to abandon the connection.
1460
'Server is too old for fast get_parent_map, reconnecting. '
1461
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1463
# To avoid having to disconnect repeatedly, we keep track of the
1464
# fact the server doesn't understand remote methods added in 1.2.
1465
medium._remember_remote_is_before((1, 2))
1466
# Recurse just once and we should use the fallback code.
1467
return self._get_parent_map_rpc(keys)
1468
response_tuple, response_handler = response
1469
if response_tuple[0] not in ['ok']:
1470
response_handler.cancel_read_body()
1471
raise errors.UnexpectedSmartServerResponse(response_tuple)
1472
if response_tuple[0] == 'ok':
1473
coded = bz2.decompress(response_handler.read_body_bytes())
1475
# no revisions found
1477
lines = coded.split('\n')
1480
d = tuple(line.split())
1482
revision_graph[d[0]] = d[1:]
1485
if d[0].startswith('missing:'):
1487
self._unstacked_provider.note_missing_key(revid)
1489
# no parents - so give the Graph result
1491
revision_graph[d[0]] = (NULL_REVISION,)
1492
return revision_graph
1495
def get_signature_text(self, revision_id):
1497
return self._real_repository.get_signature_text(revision_id)
1500
def _get_inventory_xml(self, revision_id):
1502
return self._real_repository._get_inventory_xml(revision_id)
1504
def _deserialise_inventory(self, revision_id, xml):
1506
return self._real_repository._deserialise_inventory(revision_id, xml)
1508
def reconcile(self, other=None, thorough=False):
1510
return self._real_repository.reconcile(other=other, thorough=thorough)
1512
def all_revision_ids(self):
1514
return self._real_repository.all_revision_ids()
1517
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1519
return self._real_repository.get_deltas_for_revisions(revisions,
1520
specific_fileids=specific_fileids)
1523
def get_revision_delta(self, revision_id, specific_fileids=None):
1525
return self._real_repository.get_revision_delta(revision_id,
1526
specific_fileids=specific_fileids)
1529
def revision_trees(self, revision_ids):
1531
return self._real_repository.revision_trees(revision_ids)
1534
def get_revision_reconcile(self, revision_id):
1536
return self._real_repository.get_revision_reconcile(revision_id)
1539
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1541
return self._real_repository.check(revision_ids=revision_ids,
1542
callback_refs=callback_refs, check_repo=check_repo)
1544
def copy_content_into(self, destination, revision_id=None):
1546
return self._real_repository.copy_content_into(
1547
destination, revision_id=revision_id)
1549
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1550
# get a tarball of the remote repository, and copy from that into the
1552
from bzrlib import osutils
1554
# TODO: Maybe a progress bar while streaming the tarball?
1555
note("Copying repository content as tarball...")
1556
tar_file = self._get_tarball('bz2')
1557
if tar_file is None:
1559
destination = to_bzrdir.create_repository()
1561
tar = tarfile.open('repository', fileobj=tar_file,
1563
tmpdir = osutils.mkdtemp()
1565
_extract_tar(tar, tmpdir)
1566
tmp_bzrdir = BzrDir.open(tmpdir)
1567
tmp_repo = tmp_bzrdir.open_repository()
1568
tmp_repo.copy_content_into(destination, revision_id)
1570
osutils.rmtree(tmpdir)
1574
# TODO: Suggestion from john: using external tar is much faster than
1575
# python's tarfile library, but it may not work on windows.
1578
def inventories(self):
1579
"""Decorate the real repository for now.
1581
In the long term a full blown network facility is needed to
1582
avoid creating a real repository object locally.
1585
return self._real_repository.inventories
1588
def pack(self, hint=None):
1589
"""Compress the data within the repository.
1591
This is not currently implemented within the smart server.
1594
return self._real_repository.pack(hint=hint)
1597
def revisions(self):
1598
"""Decorate the real repository for now.
1600
In the short term this should become a real object to intercept graph
1603
In the long term a full blown network facility is needed.
1606
return self._real_repository.revisions
1608
def set_make_working_trees(self, new_value):
1610
new_value_str = "True"
1612
new_value_str = "False"
1613
path = self.bzrdir._path_for_remote_call(self._client)
1615
response = self._call(
1616
'Repository.set_make_working_trees', path, new_value_str)
1617
except errors.UnknownSmartMethod:
1619
self._real_repository.set_make_working_trees(new_value)
1621
if response[0] != 'ok':
1622
raise errors.UnexpectedSmartServerResponse(response)
1625
def signatures(self):
1626
"""Decorate the real repository for now.
1628
In the long term a full blown network facility is needed to avoid
1629
creating a real repository object locally.
1632
return self._real_repository.signatures
1635
def sign_revision(self, revision_id, gpg_strategy):
1637
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1641
"""Decorate the real repository for now.
1643
In the long term a full blown network facility is needed to avoid
1644
creating a real repository object locally.
1647
return self._real_repository.texts
1650
def get_revisions(self, revision_ids):
1652
return self._real_repository.get_revisions(revision_ids)
1654
def supports_rich_root(self):
1655
return self._format.rich_root_data
1657
def iter_reverse_revision_history(self, revision_id):
1659
return self._real_repository.iter_reverse_revision_history(revision_id)
1662
def _serializer(self):
1663
return self._format._serializer
1665
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1667
return self._real_repository.store_revision_signature(
1668
gpg_strategy, plaintext, revision_id)
1670
def add_signature_text(self, revision_id, signature):
1672
return self._real_repository.add_signature_text(revision_id, signature)
1674
def has_signature_for_revision_id(self, revision_id):
1676
return self._real_repository.has_signature_for_revision_id(revision_id)
1678
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1680
return self._real_repository.item_keys_introduced_by(revision_ids,
1681
_files_pb=_files_pb)
1683
def revision_graph_can_have_wrong_parents(self):
1684
# The answer depends on the remote repo format.
1686
return self._real_repository.revision_graph_can_have_wrong_parents()
1688
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1690
return self._real_repository._find_inconsistent_revision_parents(
1693
def _check_for_inconsistent_revision_parents(self):
1695
return self._real_repository._check_for_inconsistent_revision_parents()
1697
def _make_parents_provider(self, other=None):
1698
providers = [self._unstacked_provider]
1699
if other is not None:
1700
providers.insert(0, other)
1701
providers.extend(r._make_parents_provider() for r in
1702
self._fallback_repositories)
1703
return graph.StackedParentsProvider(providers)
1705
def _serialise_search_recipe(self, recipe):
1706
"""Serialise a graph search recipe.
1708
:param recipe: A search recipe (start, stop, count).
1709
:return: Serialised bytes.
1711
start_keys = ' '.join(recipe[1])
1712
stop_keys = ' '.join(recipe[2])
1713
count = str(recipe[3])
1714
return '\n'.join((start_keys, stop_keys, count))
1716
def _serialise_search_result(self, search_result):
1717
if isinstance(search_result, graph.PendingAncestryResult):
1718
parts = ['ancestry-of']
1719
parts.extend(search_result.heads)
1721
recipe = search_result.get_recipe()
1722
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1723
return '\n'.join(parts)
1726
path = self.bzrdir._path_for_remote_call(self._client)
1728
response = self._call('PackRepository.autopack', path)
1729
except errors.UnknownSmartMethod:
1731
self._real_repository._pack_collection.autopack()
1734
if response[0] != 'ok':
1735
raise errors.UnexpectedSmartServerResponse(response)
1738
class RemoteStreamSink(repository.StreamSink):
1740
def _insert_real(self, stream, src_format, resume_tokens):
1741
self.target_repo._ensure_real()
1742
sink = self.target_repo._real_repository._get_sink()
1743
result = sink.insert_stream(stream, src_format, resume_tokens)
1745
self.target_repo.autopack()
1748
def insert_stream(self, stream, src_format, resume_tokens):
1749
target = self.target_repo
1750
target._unstacked_provider.missing_keys.clear()
1751
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1752
if target._lock_token:
1753
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1754
lock_args = (target._lock_token or '',)
1756
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1758
client = target._client
1759
medium = client._medium
1760
path = target.bzrdir._path_for_remote_call(client)
1761
# Probe for the verb to use with an empty stream before sending the
1762
# real stream to it. We do this both to avoid the risk of sending a
1763
# large request that is then rejected, and because we don't want to
1764
# implement a way to buffer, rewind, or restart the stream.
1766
for verb, required_version in candidate_calls:
1767
if medium._is_remote_before(required_version):
1770
# We've already done the probing (and set _is_remote_before) on
1771
# a previous insert.
1774
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1776
response = client.call_with_body_stream(
1777
(verb, path, '') + lock_args, byte_stream)
1778
except errors.UnknownSmartMethod:
1779
medium._remember_remote_is_before(required_version)
1785
return self._insert_real(stream, src_format, resume_tokens)
1786
self._last_inv_record = None
1787
self._last_substream = None
1788
if required_version < (1, 19):
1789
# Remote side doesn't support inventory deltas. Wrap the stream to
1790
# make sure we don't send any. If the stream contains inventory
1791
# deltas we'll interrupt the smart insert_stream request and
1793
stream = self._stop_stream_if_inventory_delta(stream)
1794
byte_stream = smart_repo._stream_to_byte_stream(
1796
resume_tokens = ' '.join(resume_tokens)
1797
response = client.call_with_body_stream(
1798
(verb, path, resume_tokens) + lock_args, byte_stream)
1799
if response[0][0] not in ('ok', 'missing-basis'):
1800
raise errors.UnexpectedSmartServerResponse(response)
1801
if self._last_substream is not None:
1802
# The stream included an inventory-delta record, but the remote
1803
# side isn't new enough to support them. So we need to send the
1804
# rest of the stream via VFS.
1805
self.target_repo.refresh_data()
1806
return self._resume_stream_with_vfs(response, src_format)
1807
if response[0][0] == 'missing-basis':
1808
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1809
resume_tokens = tokens
1810
return resume_tokens, set(missing_keys)
1812
self.target_repo.refresh_data()
1815
def _resume_stream_with_vfs(self, response, src_format):
1816
"""Resume sending a stream via VFS, first resending the record and
1817
substream that couldn't be sent via an insert_stream verb.
1819
if response[0][0] == 'missing-basis':
1820
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1821
# Ignore missing_keys, we haven't finished inserting yet
1824
def resume_substream():
1825
# Yield the substream that was interrupted.
1826
for record in self._last_substream:
1828
self._last_substream = None
1829
def resume_stream():
1830
# Finish sending the interrupted substream
1831
yield ('inventory-deltas', resume_substream())
1832
# Then simply continue sending the rest of the stream.
1833
for substream_kind, substream in self._last_stream:
1834
yield substream_kind, substream
1835
return self._insert_real(resume_stream(), src_format, tokens)
1837
def _stop_stream_if_inventory_delta(self, stream):
1838
"""Normally this just lets the original stream pass-through unchanged.
1840
However if any 'inventory-deltas' substream occurs it will stop
1841
streaming, and store the interrupted substream and stream in
1842
self._last_substream and self._last_stream so that the stream can be
1843
resumed by _resume_stream_with_vfs.
1846
stream_iter = iter(stream)
1847
for substream_kind, substream in stream_iter:
1848
if substream_kind == 'inventory-deltas':
1849
self._last_substream = substream
1850
self._last_stream = stream_iter
1853
yield substream_kind, substream
1856
class RemoteStreamSource(repository.StreamSource):
1857
"""Stream data from a remote server."""
1859
def get_stream(self, search):
1860
if (self.from_repository._fallback_repositories and
1861
self.to_format._fetch_order == 'topological'):
1862
return self._real_stream(self.from_repository, search)
1865
repos = [self.from_repository]
1871
repos.extend(repo._fallback_repositories)
1872
sources.append(repo)
1873
return self.missing_parents_chain(search, sources)
1875
def get_stream_for_missing_keys(self, missing_keys):
1876
self.from_repository._ensure_real()
1877
real_repo = self.from_repository._real_repository
1878
real_source = real_repo._get_source(self.to_format)
1879
return real_source.get_stream_for_missing_keys(missing_keys)
1881
def _real_stream(self, repo, search):
1882
"""Get a stream for search from repo.
1884
This never called RemoteStreamSource.get_stream, and is a heler
1885
for RemoteStreamSource._get_stream to allow getting a stream
1886
reliably whether fallback back because of old servers or trying
1887
to stream from a non-RemoteRepository (which the stacked support
1890
source = repo._get_source(self.to_format)
1891
if isinstance(source, RemoteStreamSource):
1893
source = repo._real_repository._get_source(self.to_format)
1894
return source.get_stream(search)
1896
def _get_stream(self, repo, search):
1897
"""Core worker to get a stream from repo for search.
1899
This is used by both get_stream and the stacking support logic. It
1900
deliberately gets a stream for repo which does not need to be
1901
self.from_repository. In the event that repo is not Remote, or
1902
cannot do a smart stream, a fallback is made to the generic
1903
repository._get_stream() interface, via self._real_stream.
1905
In the event of stacking, streams from _get_stream will not
1906
contain all the data for search - this is normal (see get_stream).
1908
:param repo: A repository.
1909
:param search: A search.
1911
# Fallbacks may be non-smart
1912
if not isinstance(repo, RemoteRepository):
1913
return self._real_stream(repo, search)
1914
client = repo._client
1915
medium = client._medium
1916
path = repo.bzrdir._path_for_remote_call(client)
1917
search_bytes = repo._serialise_search_result(search)
1918
args = (path, self.to_format.network_name())
1920
('Repository.get_stream_1.19', (1, 19)),
1921
('Repository.get_stream', (1, 13))]
1923
for verb, version in candidate_verbs:
1924
if medium._is_remote_before(version):
1927
response = repo._call_with_body_bytes_expecting_body(
1928
verb, args, search_bytes)
1929
except errors.UnknownSmartMethod:
1930
medium._remember_remote_is_before(version)
1932
response_tuple, response_handler = response
1936
return self._real_stream(repo, search)
1937
if response_tuple[0] != 'ok':
1938
raise errors.UnexpectedSmartServerResponse(response_tuple)
1939
byte_stream = response_handler.read_streamed_body()
1940
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1941
if src_format.network_name() != repo._format.network_name():
1942
raise AssertionError(
1943
"Mismatched RemoteRepository and stream src %r, %r" % (
1944
src_format.network_name(), repo._format.network_name()))
1947
def missing_parents_chain(self, search, sources):
1948
"""Chain multiple streams together to handle stacking.
1950
:param search: The overall search to satisfy with streams.
1951
:param sources: A list of Repository objects to query.
1953
self.from_serialiser = self.from_repository._format._serializer
1954
self.seen_revs = set()
1955
self.referenced_revs = set()
1956
# If there are heads in the search, or the key count is > 0, we are not
1958
while not search.is_empty() and len(sources) > 1:
1959
source = sources.pop(0)
1960
stream = self._get_stream(source, search)
1961
for kind, substream in stream:
1962
if kind != 'revisions':
1963
yield kind, substream
1965
yield kind, self.missing_parents_rev_handler(substream)
1966
search = search.refine(self.seen_revs, self.referenced_revs)
1967
self.seen_revs = set()
1968
self.referenced_revs = set()
1969
if not search.is_empty():
1970
for kind, stream in self._get_stream(sources[0], search):
1973
def missing_parents_rev_handler(self, substream):
1974
for content in substream:
1975
revision_bytes = content.get_bytes_as('fulltext')
1976
revision = self.from_serialiser.read_revision_from_string(
1978
self.seen_revs.add(content.key[-1])
1979
self.referenced_revs.update(revision.parent_ids)
1983
class RemoteBranchLockableFiles(LockableFiles):
1984
"""A 'LockableFiles' implementation that talks to a smart server.
1986
This is not a public interface class.
1989
def __init__(self, bzrdir, _client):
1990
self.bzrdir = bzrdir
1991
self._client = _client
1992
self._need_find_modes = True
1993
LockableFiles.__init__(
1994
self, bzrdir.get_branch_transport(None),
1995
'lock', lockdir.LockDir)
1997
def _find_modes(self):
1998
# RemoteBranches don't let the client set the mode of control files.
1999
self._dir_mode = None
2000
self._file_mode = None
2003
class RemoteBranchFormat(branch.BranchFormat):
2005
def __init__(self, network_name=None):
2006
super(RemoteBranchFormat, self).__init__()
2007
self._matchingbzrdir = RemoteBzrDirFormat()
2008
self._matchingbzrdir.set_branch_format(self)
2009
self._custom_format = None
2010
self._network_name = network_name
2012
def __eq__(self, other):
2013
return (isinstance(other, RemoteBranchFormat) and
2014
self.__dict__ == other.__dict__)
2016
def _ensure_real(self):
2017
if self._custom_format is None:
2018
self._custom_format = branch.network_format_registry.get(
2021
def get_format_description(self):
2023
return 'Remote: ' + self._custom_format.get_format_description()
2025
def network_name(self):
2026
return self._network_name
2028
def open(self, a_bzrdir, ignore_fallbacks=False):
2029
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2031
def _vfs_initialize(self, a_bzrdir):
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)
2040
# We assume the bzrdir is parameterised; it may not be.
2041
result = self._custom_format.initialize(a_bzrdir)
2042
if (isinstance(a_bzrdir, RemoteBzrDir) and
2043
not isinstance(result, RemoteBranch)):
2044
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2047
def initialize(self, a_bzrdir):
2048
# 1) get the network name to use.
2049
if self._custom_format:
2050
network_name = self._custom_format.network_name()
2052
# Select the current bzrlib default and ask for that.
2053
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2054
reference_format = reference_bzrdir_format.get_branch_format()
2055
self._custom_format = reference_format
2056
network_name = reference_format.network_name()
2057
# Being asked to create on a non RemoteBzrDir:
2058
if not isinstance(a_bzrdir, RemoteBzrDir):
2059
return self._vfs_initialize(a_bzrdir)
2060
medium = a_bzrdir._client._medium
2061
if medium._is_remote_before((1, 13)):
2062
return self._vfs_initialize(a_bzrdir)
2063
# Creating on a remote bzr dir.
2064
# 2) try direct creation via RPC
2065
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2066
verb = 'BzrDir.create_branch'
2068
response = a_bzrdir._call(verb, path, network_name)
2069
except errors.UnknownSmartMethod:
2070
# Fallback - use vfs methods
2071
medium._remember_remote_is_before((1, 13))
2072
return self._vfs_initialize(a_bzrdir)
2073
if response[0] != 'ok':
2074
raise errors.UnexpectedSmartServerResponse(response)
2075
# Turn the response into a RemoteRepository object.
2076
format = RemoteBranchFormat(network_name=response[1])
2077
repo_format = response_tuple_to_repo_format(response[3:])
2078
if response[2] == '':
2079
repo_bzrdir = a_bzrdir
2081
repo_bzrdir = RemoteBzrDir(
2082
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2084
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2085
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2086
format=format, setup_stacking=False)
2087
# XXX: We know this is a new branch, so it must have revno 0, revid
2088
# NULL_REVISION. Creating the branch locked would make this be unable
2089
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2090
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2091
return remote_branch
2093
def make_tags(self, branch):
2095
return self._custom_format.make_tags(branch)
2097
def supports_tags(self):
2098
# Remote branches might support tags, but we won't know until we
2099
# access the real remote branch.
2101
return self._custom_format.supports_tags()
2103
def supports_stacking(self):
2105
return self._custom_format.supports_stacking()
2107
def supports_set_append_revisions_only(self):
2109
return self._custom_format.supports_set_append_revisions_only()
2112
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2113
"""Branch stored on a server accessed by HPSS RPC.
2115
At the moment most operations are mapped down to simple file operations.
2118
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2119
_client=None, format=None, setup_stacking=True):
2120
"""Create a RemoteBranch instance.
2122
:param real_branch: An optional local implementation of the branch
2123
format, usually accessing the data via the VFS.
2124
:param _client: Private parameter for testing.
2125
:param format: A RemoteBranchFormat object, None to create one
2126
automatically. If supplied it should have a network_name already
2128
:param setup_stacking: If True make an RPC call to determine the
2129
stacked (or not) status of the branch. If False assume the branch
2132
# We intentionally don't call the parent class's __init__, because it
2133
# will try to assign to self.tags, which is a property in this subclass.
2134
# And the parent's __init__ doesn't do much anyway.
2135
self.bzrdir = remote_bzrdir
2136
if _client is not None:
2137
self._client = _client
2139
self._client = remote_bzrdir._client
2140
self.repository = remote_repository
2141
if real_branch is not None:
2142
self._real_branch = real_branch
2143
# Give the remote repository the matching real repo.
2144
real_repo = self._real_branch.repository
2145
if isinstance(real_repo, RemoteRepository):
2146
real_repo._ensure_real()
2147
real_repo = real_repo._real_repository
2148
self.repository._set_real_repository(real_repo)
2149
# Give the branch the remote repository to let fast-pathing happen.
2150
self._real_branch.repository = self.repository
2152
self._real_branch = None
2153
# Fill out expected attributes of branch for bzrlib API users.
2154
self._clear_cached_state()
2155
self.base = self.bzrdir.root_transport.base
2156
self._control_files = None
2157
self._lock_mode = None
2158
self._lock_token = None
2159
self._repo_lock_token = None
2160
self._lock_count = 0
2161
self._leave_lock = False
2162
# Setup a format: note that we cannot call _ensure_real until all the
2163
# attributes above are set: This code cannot be moved higher up in this
2166
self._format = RemoteBranchFormat()
2167
if real_branch is not None:
2168
self._format._network_name = \
2169
self._real_branch._format.network_name()
2171
self._format = format
2172
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2173
# branch.open_branch method.
2174
self._real_ignore_fallbacks = not setup_stacking
2175
if not self._format._network_name:
2176
# Did not get from open_branchV2 - old server.
2178
self._format._network_name = \
2179
self._real_branch._format.network_name()
2180
self.tags = self._format.make_tags(self)
2181
# The base class init is not called, so we duplicate this:
2182
hooks = branch.Branch.hooks['open']
2185
self._is_stacked = False
2187
self._setup_stacking()
2189
def _setup_stacking(self):
2190
# configure stacking into the remote repository, by reading it from
2193
fallback_url = self.get_stacked_on_url()
2194
except (errors.NotStacked, errors.UnstackableBranchFormat,
2195
errors.UnstackableRepositoryFormat), e:
2197
self._is_stacked = True
2198
self._activate_fallback_location(fallback_url)
2200
def _get_config(self):
2201
return RemoteBranchConfig(self)
2203
def _get_real_transport(self):
2204
# if we try vfs access, return the real branch's vfs transport
2206
return self._real_branch._transport
2208
_transport = property(_get_real_transport)
2211
return "%s(%s)" % (self.__class__.__name__, self.base)
2215
def _ensure_real(self):
2216
"""Ensure that there is a _real_branch set.
2218
Used before calls to self._real_branch.
2220
if self._real_branch is None:
2221
if not vfs.vfs_enabled():
2222
raise AssertionError('smart server vfs must be enabled '
2223
'to use vfs implementation')
2224
self.bzrdir._ensure_real()
2225
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2226
ignore_fallbacks=self._real_ignore_fallbacks)
2227
if self.repository._real_repository is None:
2228
# Give the remote repository the matching real repo.
2229
real_repo = self._real_branch.repository
2230
if isinstance(real_repo, RemoteRepository):
2231
real_repo._ensure_real()
2232
real_repo = real_repo._real_repository
2233
self.repository._set_real_repository(real_repo)
2234
# Give the real branch the remote repository to let fast-pathing
2236
self._real_branch.repository = self.repository
2237
if self._lock_mode == 'r':
2238
self._real_branch.lock_read()
2239
elif self._lock_mode == 'w':
2240
self._real_branch.lock_write(token=self._lock_token)
2242
def _translate_error(self, err, **context):
2243
self.repository._translate_error(err, branch=self, **context)
2245
def _clear_cached_state(self):
2246
super(RemoteBranch, self)._clear_cached_state()
2247
if self._real_branch is not None:
2248
self._real_branch._clear_cached_state()
2250
def _clear_cached_state_of_remote_branch_only(self):
2251
"""Like _clear_cached_state, but doesn't clear the cache of
2254
This is useful when falling back to calling a method of
2255
self._real_branch that changes state. In that case the underlying
2256
branch changes, so we need to invalidate this RemoteBranch's cache of
2257
it. However, there's no need to invalidate the _real_branch's cache
2258
too, in fact doing so might harm performance.
2260
super(RemoteBranch, self)._clear_cached_state()
2263
def control_files(self):
2264
# Defer actually creating RemoteBranchLockableFiles until its needed,
2265
# because it triggers an _ensure_real that we otherwise might not need.
2266
if self._control_files is None:
2267
self._control_files = RemoteBranchLockableFiles(
2268
self.bzrdir, self._client)
2269
return self._control_files
2271
def _get_checkout_format(self):
2273
return self._real_branch._get_checkout_format()
2275
def get_physical_lock_status(self):
2276
"""See Branch.get_physical_lock_status()."""
2277
# should be an API call to the server, as branches must be lockable.
2279
return self._real_branch.get_physical_lock_status()
2281
def get_stacked_on_url(self):
2282
"""Get the URL this branch is stacked against.
2284
:raises NotStacked: If the branch is not stacked.
2285
:raises UnstackableBranchFormat: If the branch does not support
2287
:raises UnstackableRepositoryFormat: If the repository does not support
2291
# there may not be a repository yet, so we can't use
2292
# self._translate_error, so we can't use self._call either.
2293
response = self._client.call('Branch.get_stacked_on_url',
2294
self._remote_path())
2295
except errors.ErrorFromSmartServer, err:
2296
# there may not be a repository yet, so we can't call through
2297
# its _translate_error
2298
_translate_error(err, branch=self)
2299
except errors.UnknownSmartMethod, err:
2301
return self._real_branch.get_stacked_on_url()
2302
if response[0] != 'ok':
2303
raise errors.UnexpectedSmartServerResponse(response)
2306
def set_stacked_on_url(self, url):
2307
branch.Branch.set_stacked_on_url(self, url)
2309
self._is_stacked = False
2311
self._is_stacked = True
2313
def _vfs_get_tags_bytes(self):
2315
return self._real_branch._get_tags_bytes()
2317
def _get_tags_bytes(self):
2318
medium = self._client._medium
2319
if medium._is_remote_before((1, 13)):
2320
return self._vfs_get_tags_bytes()
2322
response = self._call('Branch.get_tags_bytes', self._remote_path())
2323
except errors.UnknownSmartMethod:
2324
medium._remember_remote_is_before((1, 13))
2325
return self._vfs_get_tags_bytes()
2328
def _vfs_set_tags_bytes(self, bytes):
2330
return self._real_branch._set_tags_bytes(bytes)
2332
def _set_tags_bytes(self, bytes):
2333
medium = self._client._medium
2334
if medium._is_remote_before((1, 18)):
2335
self._vfs_set_tags_bytes(bytes)
2339
self._remote_path(), self._lock_token, self._repo_lock_token)
2340
response = self._call_with_body_bytes(
2341
'Branch.set_tags_bytes', args, bytes)
2342
except errors.UnknownSmartMethod:
2343
medium._remember_remote_is_before((1, 18))
2344
self._vfs_set_tags_bytes(bytes)
2346
def lock_read(self):
2347
self.repository.lock_read()
2348
if not self._lock_mode:
2349
self._note_lock('r')
2350
self._lock_mode = 'r'
2351
self._lock_count = 1
2352
if self._real_branch is not None:
2353
self._real_branch.lock_read()
2355
self._lock_count += 1
2357
def _remote_lock_write(self, token):
2359
branch_token = repo_token = ''
2361
branch_token = token
2362
repo_token = self.repository.lock_write()
2363
self.repository.unlock()
2364
err_context = {'token': token}
2365
response = self._call(
2366
'Branch.lock_write', self._remote_path(), branch_token,
2367
repo_token or '', **err_context)
2368
if response[0] != 'ok':
2369
raise errors.UnexpectedSmartServerResponse(response)
2370
ok, branch_token, repo_token = response
2371
return branch_token, repo_token
2373
def lock_write(self, token=None):
2374
if not self._lock_mode:
2375
self._note_lock('w')
2376
# Lock the branch and repo in one remote call.
2377
remote_tokens = self._remote_lock_write(token)
2378
self._lock_token, self._repo_lock_token = remote_tokens
2379
if not self._lock_token:
2380
raise SmartProtocolError('Remote server did not return a token!')
2381
# Tell the self.repository object that it is locked.
2382
self.repository.lock_write(
2383
self._repo_lock_token, _skip_rpc=True)
2385
if self._real_branch is not None:
2386
self._real_branch.lock_write(token=self._lock_token)
2387
if token is not None:
2388
self._leave_lock = True
2390
self._leave_lock = False
2391
self._lock_mode = 'w'
2392
self._lock_count = 1
2393
elif self._lock_mode == 'r':
2394
raise errors.ReadOnlyTransaction
2396
if token is not None:
2397
# A token was given to lock_write, and we're relocking, so
2398
# check that the given token actually matches the one we
2400
if token != self._lock_token:
2401
raise errors.TokenMismatch(token, self._lock_token)
2402
self._lock_count += 1
2403
# Re-lock the repository too.
2404
self.repository.lock_write(self._repo_lock_token)
2405
return self._lock_token or None
2407
def _unlock(self, branch_token, repo_token):
2408
err_context = {'token': str((branch_token, repo_token))}
2409
response = self._call(
2410
'Branch.unlock', self._remote_path(), branch_token,
2411
repo_token or '', **err_context)
2412
if response == ('ok',):
2414
raise errors.UnexpectedSmartServerResponse(response)
2416
@only_raises(errors.LockNotHeld, errors.LockBroken)
2419
self._lock_count -= 1
2420
if not self._lock_count:
2421
self._clear_cached_state()
2422
mode = self._lock_mode
2423
self._lock_mode = None
2424
if self._real_branch is not None:
2425
if (not self._leave_lock and mode == 'w' and
2426
self._repo_lock_token):
2427
# If this RemoteBranch will remove the physical lock
2428
# for the repository, make sure the _real_branch
2429
# doesn't do it first. (Because the _real_branch's
2430
# repository is set to be the RemoteRepository.)
2431
self._real_branch.repository.leave_lock_in_place()
2432
self._real_branch.unlock()
2434
# Only write-locked branched need to make a remote method
2435
# call to perform the unlock.
2437
if not self._lock_token:
2438
raise AssertionError('Locked, but no token!')
2439
branch_token = self._lock_token
2440
repo_token = self._repo_lock_token
2441
self._lock_token = None
2442
self._repo_lock_token = None
2443
if not self._leave_lock:
2444
self._unlock(branch_token, repo_token)
2446
self.repository.unlock()
2448
def break_lock(self):
2450
return self._real_branch.break_lock()
2452
def leave_lock_in_place(self):
2453
if not self._lock_token:
2454
raise NotImplementedError(self.leave_lock_in_place)
2455
self._leave_lock = True
2457
def dont_leave_lock_in_place(self):
2458
if not self._lock_token:
2459
raise NotImplementedError(self.dont_leave_lock_in_place)
2460
self._leave_lock = False
2463
def get_rev_id(self, revno, history=None):
2465
return _mod_revision.NULL_REVISION
2466
last_revision_info = self.last_revision_info()
2467
ok, result = self.repository.get_rev_id_for_revno(
2468
revno, last_revision_info)
2471
missing_parent = result[1]
2472
# Either the revision named by the server is missing, or its parent
2473
# is. Call get_parent_map to determine which, so that we report a
2475
parent_map = self.repository.get_parent_map([missing_parent])
2476
if missing_parent in parent_map:
2477
missing_parent = parent_map[missing_parent]
2478
raise errors.RevisionNotPresent(missing_parent, self.repository)
2480
def _last_revision_info(self):
2481
response = self._call('Branch.last_revision_info', self._remote_path())
2482
if response[0] != 'ok':
2483
raise SmartProtocolError('unexpected response code %s' % (response,))
2484
revno = int(response[1])
2485
last_revision = response[2]
2486
return (revno, last_revision)
2488
def _gen_revision_history(self):
2489
"""See Branch._gen_revision_history()."""
2490
if self._is_stacked:
2492
return self._real_branch._gen_revision_history()
2493
response_tuple, response_handler = self._call_expecting_body(
2494
'Branch.revision_history', self._remote_path())
2495
if response_tuple[0] != 'ok':
2496
raise errors.UnexpectedSmartServerResponse(response_tuple)
2497
result = response_handler.read_body_bytes().split('\x00')
2502
def _remote_path(self):
2503
return self.bzrdir._path_for_remote_call(self._client)
2505
def _set_last_revision_descendant(self, revision_id, other_branch,
2506
allow_diverged=False, allow_overwrite_descendant=False):
2507
# This performs additional work to meet the hook contract; while its
2508
# undesirable, we have to synthesise the revno to call the hook, and
2509
# not calling the hook is worse as it means changes can't be prevented.
2510
# Having calculated this though, we can't just call into
2511
# set_last_revision_info as a simple call, because there is a set_rh
2512
# hook that some folk may still be using.
2513
old_revno, old_revid = self.last_revision_info()
2514
history = self._lefthand_history(revision_id)
2515
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2516
err_context = {'other_branch': other_branch}
2517
response = self._call('Branch.set_last_revision_ex',
2518
self._remote_path(), self._lock_token, self._repo_lock_token,
2519
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2521
self._clear_cached_state()
2522
if len(response) != 3 and response[0] != 'ok':
2523
raise errors.UnexpectedSmartServerResponse(response)
2524
new_revno, new_revision_id = response[1:]
2525
self._last_revision_info_cache = new_revno, new_revision_id
2526
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2527
if self._real_branch is not None:
2528
cache = new_revno, new_revision_id
2529
self._real_branch._last_revision_info_cache = cache
2531
def _set_last_revision(self, revision_id):
2532
old_revno, old_revid = self.last_revision_info()
2533
# This performs additional work to meet the hook contract; while its
2534
# undesirable, we have to synthesise the revno to call the hook, and
2535
# not calling the hook is worse as it means changes can't be prevented.
2536
# Having calculated this though, we can't just call into
2537
# set_last_revision_info as a simple call, because there is a set_rh
2538
# hook that some folk may still be using.
2539
history = self._lefthand_history(revision_id)
2540
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2541
self._clear_cached_state()
2542
response = self._call('Branch.set_last_revision',
2543
self._remote_path(), self._lock_token, self._repo_lock_token,
2545
if response != ('ok',):
2546
raise errors.UnexpectedSmartServerResponse(response)
2547
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2550
def set_revision_history(self, rev_history):
2551
# Send just the tip revision of the history; the server will generate
2552
# the full history from that. If the revision doesn't exist in this
2553
# branch, NoSuchRevision will be raised.
2554
if rev_history == []:
2557
rev_id = rev_history[-1]
2558
self._set_last_revision(rev_id)
2559
for hook in branch.Branch.hooks['set_rh']:
2560
hook(self, rev_history)
2561
self._cache_revision_history(rev_history)
2563
def _get_parent_location(self):
2564
medium = self._client._medium
2565
if medium._is_remote_before((1, 13)):
2566
return self._vfs_get_parent_location()
2568
response = self._call('Branch.get_parent', self._remote_path())
2569
except errors.UnknownSmartMethod:
2570
medium._remember_remote_is_before((1, 13))
2571
return self._vfs_get_parent_location()
2572
if len(response) != 1:
2573
raise errors.UnexpectedSmartServerResponse(response)
2574
parent_location = response[0]
2575
if parent_location == '':
2577
return parent_location
2579
def _vfs_get_parent_location(self):
2581
return self._real_branch._get_parent_location()
2583
def _set_parent_location(self, url):
2584
medium = self._client._medium
2585
if medium._is_remote_before((1, 15)):
2586
return self._vfs_set_parent_location(url)
2588
call_url = url or ''
2589
if type(call_url) is not str:
2590
raise AssertionError('url must be a str or None (%s)' % url)
2591
response = self._call('Branch.set_parent_location',
2592
self._remote_path(), self._lock_token, self._repo_lock_token,
2594
except errors.UnknownSmartMethod:
2595
medium._remember_remote_is_before((1, 15))
2596
return self._vfs_set_parent_location(url)
2598
raise errors.UnexpectedSmartServerResponse(response)
2600
def _vfs_set_parent_location(self, url):
2602
return self._real_branch._set_parent_location(url)
2605
def pull(self, source, overwrite=False, stop_revision=None,
2607
self._clear_cached_state_of_remote_branch_only()
2609
return self._real_branch.pull(
2610
source, overwrite=overwrite, stop_revision=stop_revision,
2611
_override_hook_target=self, **kwargs)
2614
def push(self, target, overwrite=False, stop_revision=None):
2616
return self._real_branch.push(
2617
target, overwrite=overwrite, stop_revision=stop_revision,
2618
_override_hook_source_branch=self)
2620
def is_locked(self):
2621
return self._lock_count >= 1
2624
def revision_id_to_revno(self, revision_id):
2626
return self._real_branch.revision_id_to_revno(revision_id)
2629
def set_last_revision_info(self, revno, revision_id):
2630
# XXX: These should be returned by the set_last_revision_info verb
2631
old_revno, old_revid = self.last_revision_info()
2632
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2633
revision_id = ensure_null(revision_id)
2635
response = self._call('Branch.set_last_revision_info',
2636
self._remote_path(), self._lock_token, self._repo_lock_token,
2637
str(revno), revision_id)
2638
except errors.UnknownSmartMethod:
2640
self._clear_cached_state_of_remote_branch_only()
2641
self._real_branch.set_last_revision_info(revno, revision_id)
2642
self._last_revision_info_cache = revno, revision_id
2644
if response == ('ok',):
2645
self._clear_cached_state()
2646
self._last_revision_info_cache = revno, revision_id
2647
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2648
# Update the _real_branch's cache too.
2649
if self._real_branch is not None:
2650
cache = self._last_revision_info_cache
2651
self._real_branch._last_revision_info_cache = cache
2653
raise errors.UnexpectedSmartServerResponse(response)
2656
def generate_revision_history(self, revision_id, last_rev=None,
2658
medium = self._client._medium
2659
if not medium._is_remote_before((1, 6)):
2660
# Use a smart method for 1.6 and above servers
2662
self._set_last_revision_descendant(revision_id, other_branch,
2663
allow_diverged=True, allow_overwrite_descendant=True)
2665
except errors.UnknownSmartMethod:
2666
medium._remember_remote_is_before((1, 6))
2667
self._clear_cached_state_of_remote_branch_only()
2668
self.set_revision_history(self._lefthand_history(revision_id,
2669
last_rev=last_rev,other_branch=other_branch))
2671
def set_push_location(self, location):
2673
return self._real_branch.set_push_location(location)
2676
class RemoteConfig(object):
2677
"""A Config that reads and writes from smart verbs.
2679
It is a low-level object that considers config data to be name/value pairs
2680
that may be associated with a section. Assigning meaning to the these
2681
values is done at higher levels like bzrlib.config.TreeConfig.
2684
def get_option(self, name, section=None, default=None):
2685
"""Return the value associated with a named option.
2687
:param name: The name of the value
2688
:param section: The section the option is in (if any)
2689
:param default: The value to return if the value is not set
2690
:return: The value or default value
2693
configobj = self._get_configobj()
2695
section_obj = configobj
2698
section_obj = configobj[section]
2701
return section_obj.get(name, default)
2702
except errors.UnknownSmartMethod:
2703
return self._vfs_get_option(name, section, default)
2705
def _response_to_configobj(self, response):
2706
if len(response[0]) and response[0][0] != 'ok':
2707
raise errors.UnexpectedSmartServerResponse(response)
2708
lines = response[1].read_body_bytes().splitlines()
2709
return config.ConfigObj(lines, encoding='utf-8')
2712
class RemoteBranchConfig(RemoteConfig):
2713
"""A RemoteConfig for Branches."""
2715
def __init__(self, branch):
2716
self._branch = branch
2718
def _get_configobj(self):
2719
path = self._branch._remote_path()
2720
response = self._branch._client.call_expecting_body(
2721
'Branch.get_config_file', path)
2722
return self._response_to_configobj(response)
2724
def set_option(self, value, name, section=None):
2725
"""Set the value associated with a named option.
2727
:param value: The value to set
2728
:param name: The name of the value to set
2729
:param section: The section the option is in (if any)
2731
medium = self._branch._client._medium
2732
if medium._is_remote_before((1, 14)):
2733
return self._vfs_set_option(value, name, section)
2735
path = self._branch._remote_path()
2736
response = self._branch._client.call('Branch.set_config_option',
2737
path, self._branch._lock_token, self._branch._repo_lock_token,
2738
value.encode('utf8'), name, section or '')
2739
except errors.UnknownSmartMethod:
2740
medium._remember_remote_is_before((1, 14))
2741
return self._vfs_set_option(value, name, section)
2743
raise errors.UnexpectedSmartServerResponse(response)
2745
def _real_object(self):
2746
self._branch._ensure_real()
2747
return self._branch._real_branch
2749
def _vfs_set_option(self, value, name, section=None):
2750
return self._real_object()._get_config().set_option(
2751
value, name, section)
2754
class RemoteBzrDirConfig(RemoteConfig):
2755
"""A RemoteConfig for BzrDirs."""
2757
def __init__(self, bzrdir):
2758
self._bzrdir = bzrdir
2760
def _get_configobj(self):
2761
medium = self._bzrdir._client._medium
2762
verb = 'BzrDir.get_config_file'
2763
if medium._is_remote_before((1, 15)):
2764
raise errors.UnknownSmartMethod(verb)
2765
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2766
response = self._bzrdir._call_expecting_body(
2768
return self._response_to_configobj(response)
2770
def _vfs_get_option(self, name, section, default):
2771
return self._real_object()._get_config().get_option(
2772
name, section, default)
2774
def set_option(self, value, name, section=None):
2775
"""Set the value associated with a named option.
2777
:param value: The value to set
2778
:param name: The name of the value to set
2779
:param section: The section the option is in (if any)
2781
return self._real_object()._get_config().set_option(
2782
value, name, section)
2784
def _real_object(self):
2785
self._bzrdir._ensure_real()
2786
return self._bzrdir._real_bzrdir
2790
def _extract_tar(tar, to_dir):
2791
"""Extract all the contents of a tarfile object.
2793
A replacement for extractall, which is not present in python2.4
2796
tar.extract(tarinfo, to_dir)
2799
def _translate_error(err, **context):
2800
"""Translate an ErrorFromSmartServer into a more useful error.
2802
Possible context keys:
2810
If the error from the server doesn't match a known pattern, then
2811
UnknownErrorFromSmartServer is raised.
2815
return context[name]
2816
except KeyError, key_err:
2817
mutter('Missing key %r in context %r', key_err.args[0], context)
2820
"""Get the path from the context if present, otherwise use first error
2824
return context['path']
2825
except KeyError, key_err:
2827
return err.error_args[0]
2828
except IndexError, idx_err:
2830
'Missing key %r in context %r', key_err.args[0], context)
2833
if err.error_verb == 'IncompatibleRepositories':
2834
raise errors.IncompatibleRepositories(err.error_args[0],
2835
err.error_args[1], err.error_args[2])
2836
elif err.error_verb == 'NoSuchRevision':
2837
raise NoSuchRevision(find('branch'), err.error_args[0])
2838
elif err.error_verb == 'nosuchrevision':
2839
raise NoSuchRevision(find('repository'), err.error_args[0])
2840
elif err.error_verb == 'nobranch':
2841
if len(err.error_args) >= 1:
2842
extra = err.error_args[0]
2845
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2847
elif err.error_verb == 'norepository':
2848
raise errors.NoRepositoryPresent(find('bzrdir'))
2849
elif err.error_verb == 'LockContention':
2850
raise errors.LockContention('(remote lock)')
2851
elif err.error_verb == 'UnlockableTransport':
2852
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2853
elif err.error_verb == 'LockFailed':
2854
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2855
elif err.error_verb == 'TokenMismatch':
2856
raise errors.TokenMismatch(find('token'), '(remote token)')
2857
elif err.error_verb == 'Diverged':
2858
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2859
elif err.error_verb == 'TipChangeRejected':
2860
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2861
elif err.error_verb == 'UnstackableBranchFormat':
2862
raise errors.UnstackableBranchFormat(*err.error_args)
2863
elif err.error_verb == 'UnstackableRepositoryFormat':
2864
raise errors.UnstackableRepositoryFormat(*err.error_args)
2865
elif err.error_verb == 'NotStacked':
2866
raise errors.NotStacked(branch=find('branch'))
2867
elif err.error_verb == 'PermissionDenied':
2869
if len(err.error_args) >= 2:
2870
extra = err.error_args[1]
2873
raise errors.PermissionDenied(path, extra=extra)
2874
elif err.error_verb == 'ReadError':
2876
raise errors.ReadError(path)
2877
elif err.error_verb == 'NoSuchFile':
2879
raise errors.NoSuchFile(path)
2880
elif err.error_verb == 'FileExists':
2881
raise errors.FileExists(err.error_args[0])
2882
elif err.error_verb == 'DirectoryNotEmpty':
2883
raise errors.DirectoryNotEmpty(err.error_args[0])
2884
elif err.error_verb == 'ShortReadvError':
2885
args = err.error_args
2886
raise errors.ShortReadvError(
2887
args[0], int(args[1]), int(args[2]), int(args[3]))
2888
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2889
encoding = str(err.error_args[0]) # encoding must always be a string
2890
val = err.error_args[1]
2891
start = int(err.error_args[2])
2892
end = int(err.error_args[3])
2893
reason = str(err.error_args[4]) # reason must always be a string
2894
if val.startswith('u:'):
2895
val = val[2:].decode('utf-8')
2896
elif val.startswith('s:'):
2897
val = val[2:].decode('base64')
2898
if err.error_verb == 'UnicodeDecodeError':
2899
raise UnicodeDecodeError(encoding, val, start, end, reason)
2900
elif err.error_verb == 'UnicodeEncodeError':
2901
raise UnicodeEncodeError(encoding, val, start, end, reason)
2902
elif err.error_verb == 'ReadOnlyError':
2903
raise errors.TransportNotPossible('readonly transport')
2904
raise errors.UnknownErrorFromSmartServer(err)