1
# Copyright (C) 2006-2010 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
31
revision as _mod_revision,
34
from bzrlib.branch import BranchReferenceFormat
35
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
36
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
37
from bzrlib.errors import (
41
from bzrlib.lockable_files import LockableFiles
42
from bzrlib.smart import client, vfs, repository as smart_repo
43
from bzrlib.revision import ensure_null, NULL_REVISION
44
from bzrlib.trace import mutter, note, warning
47
class _RpcHelper(object):
48
"""Mixin class that helps with issuing RPCs."""
50
def _call(self, method, *args, **err_context):
52
return self._client.call(method, *args)
53
except errors.ErrorFromSmartServer, err:
54
self._translate_error(err, **err_context)
56
def _call_expecting_body(self, method, *args, **err_context):
58
return self._client.call_expecting_body(method, *args)
59
except errors.ErrorFromSmartServer, err:
60
self._translate_error(err, **err_context)
62
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
64
return self._client.call_with_body_bytes(method, args, body_bytes)
65
except errors.ErrorFromSmartServer, err:
66
self._translate_error(err, **err_context)
68
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
71
return self._client.call_with_body_bytes_expecting_body(
72
method, args, body_bytes)
73
except errors.ErrorFromSmartServer, err:
74
self._translate_error(err, **err_context)
77
def response_tuple_to_repo_format(response):
78
"""Convert a response tuple describing a repository format to a format."""
79
format = RemoteRepositoryFormat()
80
format._rich_root_data = (response[0] == 'yes')
81
format._supports_tree_reference = (response[1] == 'yes')
82
format._supports_external_lookups = (response[2] == 'yes')
83
format._network_name = response[3]
87
# Note: RemoteBzrDirFormat is in bzrdir.py
89
class RemoteBzrDir(BzrDir, _RpcHelper):
90
"""Control directory on a remote server, accessed via bzr:// or similar."""
92
def __init__(self, transport, format, _client=None, _force_probe=False):
93
"""Construct a RemoteBzrDir.
95
:param _client: Private parameter for testing. Disables probing and the
98
BzrDir.__init__(self, transport, format)
99
# this object holds a delegated bzrdir that uses file-level operations
100
# to talk to the other side
101
self._real_bzrdir = None
102
self._has_working_tree = None
103
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
104
# create_branch for details.
105
self._next_open_branch_result = None
108
medium = transport.get_smart_medium()
109
self._client = client._SmartClient(medium)
111
self._client = _client
118
return '%s(%r)' % (self.__class__.__name__, self._client)
120
def _probe_bzrdir(self):
121
medium = self._client._medium
122
path = self._path_for_remote_call(self._client)
123
if medium._is_remote_before((2, 1)):
127
self._rpc_open_2_1(path)
129
except errors.UnknownSmartMethod:
130
medium._remember_remote_is_before((2, 1))
133
def _rpc_open_2_1(self, path):
134
response = self._call('BzrDir.open_2.1', path)
135
if response == ('no',):
136
raise errors.NotBranchError(path=self.root_transport.base)
137
elif response[0] == 'yes':
138
if response[1] == 'yes':
139
self._has_working_tree = True
140
elif response[1] == 'no':
141
self._has_working_tree = False
143
raise errors.UnexpectedSmartServerResponse(response)
145
raise errors.UnexpectedSmartServerResponse(response)
147
def _rpc_open(self, path):
148
response = self._call('BzrDir.open', path)
149
if response not in [('yes',), ('no',)]:
150
raise errors.UnexpectedSmartServerResponse(response)
151
if response == ('no',):
152
raise errors.NotBranchError(path=self.root_transport.base)
154
def _ensure_real(self):
155
"""Ensure that there is a _real_bzrdir set.
157
Used before calls to self._real_bzrdir.
159
if not self._real_bzrdir:
160
if 'hpssvfs' in debug.debug_flags:
162
warning('VFS BzrDir access triggered\n%s',
163
''.join(traceback.format_stack()))
164
self._real_bzrdir = BzrDir.open_from_transport(
165
self.root_transport, _server_formats=False)
166
self._format._network_name = \
167
self._real_bzrdir._format.network_name()
169
def _translate_error(self, err, **context):
170
_translate_error(err, bzrdir=self, **context)
172
def break_lock(self):
173
# Prevent aliasing problems in the next_open_branch_result cache.
174
# See create_branch for rationale.
175
self._next_open_branch_result = None
176
return BzrDir.break_lock(self)
178
def _vfs_cloning_metadir(self, require_stacking=False):
180
return self._real_bzrdir.cloning_metadir(
181
require_stacking=require_stacking)
183
def cloning_metadir(self, require_stacking=False):
184
medium = self._client._medium
185
if medium._is_remote_before((1, 13)):
186
return self._vfs_cloning_metadir(require_stacking=require_stacking)
187
verb = 'BzrDir.cloning_metadir'
192
path = self._path_for_remote_call(self._client)
194
response = self._call(verb, path, stacking)
195
except errors.UnknownSmartMethod:
196
medium._remember_remote_is_before((1, 13))
197
return self._vfs_cloning_metadir(require_stacking=require_stacking)
198
except errors.UnknownErrorFromSmartServer, err:
199
if err.error_tuple != ('BranchReference',):
201
# We need to resolve the branch reference to determine the
202
# cloning_metadir. This causes unnecessary RPCs to open the
203
# referenced branch (and bzrdir, etc) but only when the caller
204
# didn't already resolve the branch reference.
205
referenced_branch = self.open_branch()
206
return referenced_branch.bzrdir.cloning_metadir()
207
if len(response) != 3:
208
raise errors.UnexpectedSmartServerResponse(response)
209
control_name, repo_name, branch_info = response
210
if len(branch_info) != 2:
211
raise errors.UnexpectedSmartServerResponse(response)
212
branch_ref, branch_name = branch_info
213
format = bzrdir.network_format_registry.get(control_name)
215
format.repository_format = repository.network_format_registry.get(
217
if branch_ref == 'ref':
218
# XXX: we need possible_transports here to avoid reopening the
219
# connection to the referenced location
220
ref_bzrdir = BzrDir.open(branch_name)
221
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
222
format.set_branch_format(branch_format)
223
elif branch_ref == 'branch':
225
format.set_branch_format(
226
branch.network_format_registry.get(branch_name))
228
raise errors.UnexpectedSmartServerResponse(response)
231
def create_repository(self, shared=False):
232
# as per meta1 formats - just delegate to the format object which may
234
result = self._format.repository_format.initialize(self, shared)
235
if not isinstance(result, RemoteRepository):
236
return self.open_repository()
240
def destroy_repository(self):
241
"""See BzrDir.destroy_repository"""
243
self._real_bzrdir.destroy_repository()
245
def create_branch(self):
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 reconcile(self, other=None, thorough=False):
1506
return self._real_repository.reconcile(other=other, thorough=thorough)
1508
def all_revision_ids(self):
1510
return self._real_repository.all_revision_ids()
1513
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1515
return self._real_repository.get_deltas_for_revisions(revisions,
1516
specific_fileids=specific_fileids)
1519
def get_revision_delta(self, revision_id, specific_fileids=None):
1521
return self._real_repository.get_revision_delta(revision_id,
1522
specific_fileids=specific_fileids)
1525
def revision_trees(self, revision_ids):
1527
return self._real_repository.revision_trees(revision_ids)
1530
def get_revision_reconcile(self, revision_id):
1532
return self._real_repository.get_revision_reconcile(revision_id)
1535
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1537
return self._real_repository.check(revision_ids=revision_ids,
1538
callback_refs=callback_refs, check_repo=check_repo)
1540
def copy_content_into(self, destination, revision_id=None):
1542
return self._real_repository.copy_content_into(
1543
destination, revision_id=revision_id)
1545
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1546
# get a tarball of the remote repository, and copy from that into the
1548
from bzrlib import osutils
1550
# TODO: Maybe a progress bar while streaming the tarball?
1551
note("Copying repository content as tarball...")
1552
tar_file = self._get_tarball('bz2')
1553
if tar_file is None:
1555
destination = to_bzrdir.create_repository()
1557
tar = tarfile.open('repository', fileobj=tar_file,
1559
tmpdir = osutils.mkdtemp()
1561
_extract_tar(tar, tmpdir)
1562
tmp_bzrdir = BzrDir.open(tmpdir)
1563
tmp_repo = tmp_bzrdir.open_repository()
1564
tmp_repo.copy_content_into(destination, revision_id)
1566
osutils.rmtree(tmpdir)
1570
# TODO: Suggestion from john: using external tar is much faster than
1571
# python's tarfile library, but it may not work on windows.
1574
def inventories(self):
1575
"""Decorate the real repository for now.
1577
In the long term a full blown network facility is needed to
1578
avoid creating a real repository object locally.
1581
return self._real_repository.inventories
1584
def pack(self, hint=None):
1585
"""Compress the data within the repository.
1587
This is not currently implemented within the smart server.
1590
return self._real_repository.pack(hint=hint)
1593
def revisions(self):
1594
"""Decorate the real repository for now.
1596
In the short term this should become a real object to intercept graph
1599
In the long term a full blown network facility is needed.
1602
return self._real_repository.revisions
1604
def set_make_working_trees(self, new_value):
1606
new_value_str = "True"
1608
new_value_str = "False"
1609
path = self.bzrdir._path_for_remote_call(self._client)
1611
response = self._call(
1612
'Repository.set_make_working_trees', path, new_value_str)
1613
except errors.UnknownSmartMethod:
1615
self._real_repository.set_make_working_trees(new_value)
1617
if response[0] != 'ok':
1618
raise errors.UnexpectedSmartServerResponse(response)
1621
def signatures(self):
1622
"""Decorate the real repository for now.
1624
In the long term a full blown network facility is needed to avoid
1625
creating a real repository object locally.
1628
return self._real_repository.signatures
1631
def sign_revision(self, revision_id, gpg_strategy):
1633
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1637
"""Decorate the real repository for now.
1639
In the long term a full blown network facility is needed to avoid
1640
creating a real repository object locally.
1643
return self._real_repository.texts
1646
def get_revisions(self, revision_ids):
1648
return self._real_repository.get_revisions(revision_ids)
1650
def supports_rich_root(self):
1651
return self._format.rich_root_data
1653
def iter_reverse_revision_history(self, revision_id):
1655
return self._real_repository.iter_reverse_revision_history(revision_id)
1658
def _serializer(self):
1659
return self._format._serializer
1661
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1663
return self._real_repository.store_revision_signature(
1664
gpg_strategy, plaintext, revision_id)
1666
def add_signature_text(self, revision_id, signature):
1668
return self._real_repository.add_signature_text(revision_id, signature)
1670
def has_signature_for_revision_id(self, revision_id):
1672
return self._real_repository.has_signature_for_revision_id(revision_id)
1674
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1676
return self._real_repository.item_keys_introduced_by(revision_ids,
1677
_files_pb=_files_pb)
1679
def revision_graph_can_have_wrong_parents(self):
1680
# The answer depends on the remote repo format.
1682
return self._real_repository.revision_graph_can_have_wrong_parents()
1684
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1686
return self._real_repository._find_inconsistent_revision_parents(
1689
def _check_for_inconsistent_revision_parents(self):
1691
return self._real_repository._check_for_inconsistent_revision_parents()
1693
def _make_parents_provider(self, other=None):
1694
providers = [self._unstacked_provider]
1695
if other is not None:
1696
providers.insert(0, other)
1697
providers.extend(r._make_parents_provider() for r in
1698
self._fallback_repositories)
1699
return graph.StackedParentsProvider(providers)
1701
def _serialise_search_recipe(self, recipe):
1702
"""Serialise a graph search recipe.
1704
:param recipe: A search recipe (start, stop, count).
1705
:return: Serialised bytes.
1707
start_keys = ' '.join(recipe[1])
1708
stop_keys = ' '.join(recipe[2])
1709
count = str(recipe[3])
1710
return '\n'.join((start_keys, stop_keys, count))
1712
def _serialise_search_result(self, search_result):
1713
if isinstance(search_result, graph.PendingAncestryResult):
1714
parts = ['ancestry-of']
1715
parts.extend(search_result.heads)
1717
recipe = search_result.get_recipe()
1718
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1719
return '\n'.join(parts)
1722
path = self.bzrdir._path_for_remote_call(self._client)
1724
response = self._call('PackRepository.autopack', path)
1725
except errors.UnknownSmartMethod:
1727
self._real_repository._pack_collection.autopack()
1730
if response[0] != 'ok':
1731
raise errors.UnexpectedSmartServerResponse(response)
1734
class RemoteStreamSink(repository.StreamSink):
1736
def _insert_real(self, stream, src_format, resume_tokens):
1737
self.target_repo._ensure_real()
1738
sink = self.target_repo._real_repository._get_sink()
1739
result = sink.insert_stream(stream, src_format, resume_tokens)
1741
self.target_repo.autopack()
1744
def insert_stream(self, stream, src_format, resume_tokens):
1745
target = self.target_repo
1746
target._unstacked_provider.missing_keys.clear()
1747
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1748
if target._lock_token:
1749
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1750
lock_args = (target._lock_token or '',)
1752
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1754
client = target._client
1755
medium = client._medium
1756
path = target.bzrdir._path_for_remote_call(client)
1757
# Probe for the verb to use with an empty stream before sending the
1758
# real stream to it. We do this both to avoid the risk of sending a
1759
# large request that is then rejected, and because we don't want to
1760
# implement a way to buffer, rewind, or restart the stream.
1762
for verb, required_version in candidate_calls:
1763
if medium._is_remote_before(required_version):
1766
# We've already done the probing (and set _is_remote_before) on
1767
# a previous insert.
1770
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1772
response = client.call_with_body_stream(
1773
(verb, path, '') + lock_args, byte_stream)
1774
except errors.UnknownSmartMethod:
1775
medium._remember_remote_is_before(required_version)
1781
return self._insert_real(stream, src_format, resume_tokens)
1782
self._last_inv_record = None
1783
self._last_substream = None
1784
if required_version < (1, 19):
1785
# Remote side doesn't support inventory deltas. Wrap the stream to
1786
# make sure we don't send any. If the stream contains inventory
1787
# deltas we'll interrupt the smart insert_stream request and
1789
stream = self._stop_stream_if_inventory_delta(stream)
1790
byte_stream = smart_repo._stream_to_byte_stream(
1792
resume_tokens = ' '.join(resume_tokens)
1793
response = client.call_with_body_stream(
1794
(verb, path, resume_tokens) + lock_args, byte_stream)
1795
if response[0][0] not in ('ok', 'missing-basis'):
1796
raise errors.UnexpectedSmartServerResponse(response)
1797
if self._last_substream is not None:
1798
# The stream included an inventory-delta record, but the remote
1799
# side isn't new enough to support them. So we need to send the
1800
# rest of the stream via VFS.
1801
self.target_repo.refresh_data()
1802
return self._resume_stream_with_vfs(response, src_format)
1803
if response[0][0] == 'missing-basis':
1804
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1805
resume_tokens = tokens
1806
return resume_tokens, set(missing_keys)
1808
self.target_repo.refresh_data()
1811
def _resume_stream_with_vfs(self, response, src_format):
1812
"""Resume sending a stream via VFS, first resending the record and
1813
substream that couldn't be sent via an insert_stream verb.
1815
if response[0][0] == 'missing-basis':
1816
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1817
# Ignore missing_keys, we haven't finished inserting yet
1820
def resume_substream():
1821
# Yield the substream that was interrupted.
1822
for record in self._last_substream:
1824
self._last_substream = None
1825
def resume_stream():
1826
# Finish sending the interrupted substream
1827
yield ('inventory-deltas', resume_substream())
1828
# Then simply continue sending the rest of the stream.
1829
for substream_kind, substream in self._last_stream:
1830
yield substream_kind, substream
1831
return self._insert_real(resume_stream(), src_format, tokens)
1833
def _stop_stream_if_inventory_delta(self, stream):
1834
"""Normally this just lets the original stream pass-through unchanged.
1836
However if any 'inventory-deltas' substream occurs it will stop
1837
streaming, and store the interrupted substream and stream in
1838
self._last_substream and self._last_stream so that the stream can be
1839
resumed by _resume_stream_with_vfs.
1842
stream_iter = iter(stream)
1843
for substream_kind, substream in stream_iter:
1844
if substream_kind == 'inventory-deltas':
1845
self._last_substream = substream
1846
self._last_stream = stream_iter
1849
yield substream_kind, substream
1852
class RemoteStreamSource(repository.StreamSource):
1853
"""Stream data from a remote server."""
1855
def get_stream(self, search):
1856
if (self.from_repository._fallback_repositories and
1857
self.to_format._fetch_order == 'topological'):
1858
return self._real_stream(self.from_repository, search)
1861
repos = [self.from_repository]
1867
repos.extend(repo._fallback_repositories)
1868
sources.append(repo)
1869
return self.missing_parents_chain(search, sources)
1871
def get_stream_for_missing_keys(self, missing_keys):
1872
self.from_repository._ensure_real()
1873
real_repo = self.from_repository._real_repository
1874
real_source = real_repo._get_source(self.to_format)
1875
return real_source.get_stream_for_missing_keys(missing_keys)
1877
def _real_stream(self, repo, search):
1878
"""Get a stream for search from repo.
1880
This never called RemoteStreamSource.get_stream, and is a heler
1881
for RemoteStreamSource._get_stream to allow getting a stream
1882
reliably whether fallback back because of old servers or trying
1883
to stream from a non-RemoteRepository (which the stacked support
1886
source = repo._get_source(self.to_format)
1887
if isinstance(source, RemoteStreamSource):
1889
source = repo._real_repository._get_source(self.to_format)
1890
return source.get_stream(search)
1892
def _get_stream(self, repo, search):
1893
"""Core worker to get a stream from repo for search.
1895
This is used by both get_stream and the stacking support logic. It
1896
deliberately gets a stream for repo which does not need to be
1897
self.from_repository. In the event that repo is not Remote, or
1898
cannot do a smart stream, a fallback is made to the generic
1899
repository._get_stream() interface, via self._real_stream.
1901
In the event of stacking, streams from _get_stream will not
1902
contain all the data for search - this is normal (see get_stream).
1904
:param repo: A repository.
1905
:param search: A search.
1907
# Fallbacks may be non-smart
1908
if not isinstance(repo, RemoteRepository):
1909
return self._real_stream(repo, search)
1910
client = repo._client
1911
medium = client._medium
1912
path = repo.bzrdir._path_for_remote_call(client)
1913
search_bytes = repo._serialise_search_result(search)
1914
args = (path, self.to_format.network_name())
1916
('Repository.get_stream_1.19', (1, 19)),
1917
('Repository.get_stream', (1, 13))]
1919
for verb, version in candidate_verbs:
1920
if medium._is_remote_before(version):
1923
response = repo._call_with_body_bytes_expecting_body(
1924
verb, args, search_bytes)
1925
except errors.UnknownSmartMethod:
1926
medium._remember_remote_is_before(version)
1928
response_tuple, response_handler = response
1932
return self._real_stream(repo, search)
1933
if response_tuple[0] != 'ok':
1934
raise errors.UnexpectedSmartServerResponse(response_tuple)
1935
byte_stream = response_handler.read_streamed_body()
1936
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1937
if src_format.network_name() != repo._format.network_name():
1938
raise AssertionError(
1939
"Mismatched RemoteRepository and stream src %r, %r" % (
1940
src_format.network_name(), repo._format.network_name()))
1943
def missing_parents_chain(self, search, sources):
1944
"""Chain multiple streams together to handle stacking.
1946
:param search: The overall search to satisfy with streams.
1947
:param sources: A list of Repository objects to query.
1949
self.from_serialiser = self.from_repository._format._serializer
1950
self.seen_revs = set()
1951
self.referenced_revs = set()
1952
# If there are heads in the search, or the key count is > 0, we are not
1954
while not search.is_empty() and len(sources) > 1:
1955
source = sources.pop(0)
1956
stream = self._get_stream(source, search)
1957
for kind, substream in stream:
1958
if kind != 'revisions':
1959
yield kind, substream
1961
yield kind, self.missing_parents_rev_handler(substream)
1962
search = search.refine(self.seen_revs, self.referenced_revs)
1963
self.seen_revs = set()
1964
self.referenced_revs = set()
1965
if not search.is_empty():
1966
for kind, stream in self._get_stream(sources[0], search):
1969
def missing_parents_rev_handler(self, substream):
1970
for content in substream:
1971
revision_bytes = content.get_bytes_as('fulltext')
1972
revision = self.from_serialiser.read_revision_from_string(
1974
self.seen_revs.add(content.key[-1])
1975
self.referenced_revs.update(revision.parent_ids)
1979
class RemoteBranchLockableFiles(LockableFiles):
1980
"""A 'LockableFiles' implementation that talks to a smart server.
1982
This is not a public interface class.
1985
def __init__(self, bzrdir, _client):
1986
self.bzrdir = bzrdir
1987
self._client = _client
1988
self._need_find_modes = True
1989
LockableFiles.__init__(
1990
self, bzrdir.get_branch_transport(None),
1991
'lock', lockdir.LockDir)
1993
def _find_modes(self):
1994
# RemoteBranches don't let the client set the mode of control files.
1995
self._dir_mode = None
1996
self._file_mode = None
1999
class RemoteBranchFormat(branch.BranchFormat):
2001
def __init__(self, network_name=None):
2002
super(RemoteBranchFormat, self).__init__()
2003
self._matchingbzrdir = RemoteBzrDirFormat()
2004
self._matchingbzrdir.set_branch_format(self)
2005
self._custom_format = None
2006
self._network_name = network_name
2008
def __eq__(self, other):
2009
return (isinstance(other, RemoteBranchFormat) and
2010
self.__dict__ == other.__dict__)
2012
def _ensure_real(self):
2013
if self._custom_format is None:
2014
self._custom_format = branch.network_format_registry.get(
2017
def get_format_description(self):
2019
return 'Remote: ' + self._custom_format.get_format_description()
2021
def network_name(self):
2022
return self._network_name
2024
def open(self, a_bzrdir, ignore_fallbacks=False):
2025
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2027
def _vfs_initialize(self, a_bzrdir):
2028
# Initialisation when using a local bzrdir object, or a non-vfs init
2029
# method is not available on the server.
2030
# self._custom_format is always set - the start of initialize ensures
2032
if isinstance(a_bzrdir, RemoteBzrDir):
2033
a_bzrdir._ensure_real()
2034
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2036
# We assume the bzrdir is parameterised; it may not be.
2037
result = self._custom_format.initialize(a_bzrdir)
2038
if (isinstance(a_bzrdir, RemoteBzrDir) and
2039
not isinstance(result, RemoteBranch)):
2040
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2043
def initialize(self, a_bzrdir):
2044
# 1) get the network name to use.
2045
if self._custom_format:
2046
network_name = self._custom_format.network_name()
2048
# Select the current bzrlib default and ask for that.
2049
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2050
reference_format = reference_bzrdir_format.get_branch_format()
2051
self._custom_format = reference_format
2052
network_name = reference_format.network_name()
2053
# Being asked to create on a non RemoteBzrDir:
2054
if not isinstance(a_bzrdir, RemoteBzrDir):
2055
return self._vfs_initialize(a_bzrdir)
2056
medium = a_bzrdir._client._medium
2057
if medium._is_remote_before((1, 13)):
2058
return self._vfs_initialize(a_bzrdir)
2059
# Creating on a remote bzr dir.
2060
# 2) try direct creation via RPC
2061
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2062
verb = 'BzrDir.create_branch'
2064
response = a_bzrdir._call(verb, path, network_name)
2065
except errors.UnknownSmartMethod:
2066
# Fallback - use vfs methods
2067
medium._remember_remote_is_before((1, 13))
2068
return self._vfs_initialize(a_bzrdir)
2069
if response[0] != 'ok':
2070
raise errors.UnexpectedSmartServerResponse(response)
2071
# Turn the response into a RemoteRepository object.
2072
format = RemoteBranchFormat(network_name=response[1])
2073
repo_format = response_tuple_to_repo_format(response[3:])
2074
if response[2] == '':
2075
repo_bzrdir = a_bzrdir
2077
repo_bzrdir = RemoteBzrDir(
2078
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2080
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2081
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2082
format=format, setup_stacking=False)
2083
# XXX: We know this is a new branch, so it must have revno 0, revid
2084
# NULL_REVISION. Creating the branch locked would make this be unable
2085
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2086
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2087
return remote_branch
2089
def make_tags(self, branch):
2091
return self._custom_format.make_tags(branch)
2093
def supports_tags(self):
2094
# Remote branches might support tags, but we won't know until we
2095
# access the real remote branch.
2097
return self._custom_format.supports_tags()
2099
def supports_stacking(self):
2101
return self._custom_format.supports_stacking()
2103
def supports_set_append_revisions_only(self):
2105
return self._custom_format.supports_set_append_revisions_only()
2108
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2109
"""Branch stored on a server accessed by HPSS RPC.
2111
At the moment most operations are mapped down to simple file operations.
2114
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2115
_client=None, format=None, setup_stacking=True):
2116
"""Create a RemoteBranch instance.
2118
:param real_branch: An optional local implementation of the branch
2119
format, usually accessing the data via the VFS.
2120
:param _client: Private parameter for testing.
2121
:param format: A RemoteBranchFormat object, None to create one
2122
automatically. If supplied it should have a network_name already
2124
:param setup_stacking: If True make an RPC call to determine the
2125
stacked (or not) status of the branch. If False assume the branch
2128
# We intentionally don't call the parent class's __init__, because it
2129
# will try to assign to self.tags, which is a property in this subclass.
2130
# And the parent's __init__ doesn't do much anyway.
2131
self.bzrdir = remote_bzrdir
2132
if _client is not None:
2133
self._client = _client
2135
self._client = remote_bzrdir._client
2136
self.repository = remote_repository
2137
if real_branch is not None:
2138
self._real_branch = real_branch
2139
# Give the remote repository the matching real repo.
2140
real_repo = self._real_branch.repository
2141
if isinstance(real_repo, RemoteRepository):
2142
real_repo._ensure_real()
2143
real_repo = real_repo._real_repository
2144
self.repository._set_real_repository(real_repo)
2145
# Give the branch the remote repository to let fast-pathing happen.
2146
self._real_branch.repository = self.repository
2148
self._real_branch = None
2149
# Fill out expected attributes of branch for bzrlib API users.
2150
self._clear_cached_state()
2151
self.base = self.bzrdir.root_transport.base
2152
self._control_files = None
2153
self._lock_mode = None
2154
self._lock_token = None
2155
self._repo_lock_token = None
2156
self._lock_count = 0
2157
self._leave_lock = False
2158
# Setup a format: note that we cannot call _ensure_real until all the
2159
# attributes above are set: This code cannot be moved higher up in this
2162
self._format = RemoteBranchFormat()
2163
if real_branch is not None:
2164
self._format._network_name = \
2165
self._real_branch._format.network_name()
2167
self._format = format
2168
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2169
# branch.open_branch method.
2170
self._real_ignore_fallbacks = not setup_stacking
2171
if not self._format._network_name:
2172
# Did not get from open_branchV2 - old server.
2174
self._format._network_name = \
2175
self._real_branch._format.network_name()
2176
self.tags = self._format.make_tags(self)
2177
# The base class init is not called, so we duplicate this:
2178
hooks = branch.Branch.hooks['open']
2181
self._is_stacked = False
2183
self._setup_stacking()
2185
def _setup_stacking(self):
2186
# configure stacking into the remote repository, by reading it from
2189
fallback_url = self.get_stacked_on_url()
2190
except (errors.NotStacked, errors.UnstackableBranchFormat,
2191
errors.UnstackableRepositoryFormat), e:
2193
self._is_stacked = True
2194
self._activate_fallback_location(fallback_url)
2196
def _get_config(self):
2197
return RemoteBranchConfig(self)
2199
def _get_real_transport(self):
2200
# if we try vfs access, return the real branch's vfs transport
2202
return self._real_branch._transport
2204
_transport = property(_get_real_transport)
2207
return "%s(%s)" % (self.__class__.__name__, self.base)
2211
def _ensure_real(self):
2212
"""Ensure that there is a _real_branch set.
2214
Used before calls to self._real_branch.
2216
if self._real_branch is None:
2217
if not vfs.vfs_enabled():
2218
raise AssertionError('smart server vfs must be enabled '
2219
'to use vfs implementation')
2220
self.bzrdir._ensure_real()
2221
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2222
ignore_fallbacks=self._real_ignore_fallbacks)
2223
if self.repository._real_repository is None:
2224
# Give the remote repository the matching real repo.
2225
real_repo = self._real_branch.repository
2226
if isinstance(real_repo, RemoteRepository):
2227
real_repo._ensure_real()
2228
real_repo = real_repo._real_repository
2229
self.repository._set_real_repository(real_repo)
2230
# Give the real branch the remote repository to let fast-pathing
2232
self._real_branch.repository = self.repository
2233
if self._lock_mode == 'r':
2234
self._real_branch.lock_read()
2235
elif self._lock_mode == 'w':
2236
self._real_branch.lock_write(token=self._lock_token)
2238
def _translate_error(self, err, **context):
2239
self.repository._translate_error(err, branch=self, **context)
2241
def _clear_cached_state(self):
2242
super(RemoteBranch, self)._clear_cached_state()
2243
if self._real_branch is not None:
2244
self._real_branch._clear_cached_state()
2246
def _clear_cached_state_of_remote_branch_only(self):
2247
"""Like _clear_cached_state, but doesn't clear the cache of
2250
This is useful when falling back to calling a method of
2251
self._real_branch that changes state. In that case the underlying
2252
branch changes, so we need to invalidate this RemoteBranch's cache of
2253
it. However, there's no need to invalidate the _real_branch's cache
2254
too, in fact doing so might harm performance.
2256
super(RemoteBranch, self)._clear_cached_state()
2259
def control_files(self):
2260
# Defer actually creating RemoteBranchLockableFiles until its needed,
2261
# because it triggers an _ensure_real that we otherwise might not need.
2262
if self._control_files is None:
2263
self._control_files = RemoteBranchLockableFiles(
2264
self.bzrdir, self._client)
2265
return self._control_files
2267
def _get_checkout_format(self):
2269
return self._real_branch._get_checkout_format()
2271
def get_physical_lock_status(self):
2272
"""See Branch.get_physical_lock_status()."""
2273
# should be an API call to the server, as branches must be lockable.
2275
return self._real_branch.get_physical_lock_status()
2277
def get_stacked_on_url(self):
2278
"""Get the URL this branch is stacked against.
2280
:raises NotStacked: If the branch is not stacked.
2281
:raises UnstackableBranchFormat: If the branch does not support
2283
:raises UnstackableRepositoryFormat: If the repository does not support
2287
# there may not be a repository yet, so we can't use
2288
# self._translate_error, so we can't use self._call either.
2289
response = self._client.call('Branch.get_stacked_on_url',
2290
self._remote_path())
2291
except errors.ErrorFromSmartServer, err:
2292
# there may not be a repository yet, so we can't call through
2293
# its _translate_error
2294
_translate_error(err, branch=self)
2295
except errors.UnknownSmartMethod, err:
2297
return self._real_branch.get_stacked_on_url()
2298
if response[0] != 'ok':
2299
raise errors.UnexpectedSmartServerResponse(response)
2302
def set_stacked_on_url(self, url):
2303
branch.Branch.set_stacked_on_url(self, url)
2305
self._is_stacked = False
2307
self._is_stacked = True
2309
def _vfs_get_tags_bytes(self):
2311
return self._real_branch._get_tags_bytes()
2313
def _get_tags_bytes(self):
2314
medium = self._client._medium
2315
if medium._is_remote_before((1, 13)):
2316
return self._vfs_get_tags_bytes()
2318
response = self._call('Branch.get_tags_bytes', self._remote_path())
2319
except errors.UnknownSmartMethod:
2320
medium._remember_remote_is_before((1, 13))
2321
return self._vfs_get_tags_bytes()
2324
def _vfs_set_tags_bytes(self, bytes):
2326
return self._real_branch._set_tags_bytes(bytes)
2328
def _set_tags_bytes(self, bytes):
2329
medium = self._client._medium
2330
if medium._is_remote_before((1, 18)):
2331
self._vfs_set_tags_bytes(bytes)
2335
self._remote_path(), self._lock_token, self._repo_lock_token)
2336
response = self._call_with_body_bytes(
2337
'Branch.set_tags_bytes', args, bytes)
2338
except errors.UnknownSmartMethod:
2339
medium._remember_remote_is_before((1, 18))
2340
self._vfs_set_tags_bytes(bytes)
2342
def lock_read(self):
2343
self.repository.lock_read()
2344
if not self._lock_mode:
2345
self._note_lock('r')
2346
self._lock_mode = 'r'
2347
self._lock_count = 1
2348
if self._real_branch is not None:
2349
self._real_branch.lock_read()
2351
self._lock_count += 1
2353
def _remote_lock_write(self, token):
2355
branch_token = repo_token = ''
2357
branch_token = token
2358
repo_token = self.repository.lock_write()
2359
self.repository.unlock()
2360
err_context = {'token': token}
2361
response = self._call(
2362
'Branch.lock_write', self._remote_path(), branch_token,
2363
repo_token or '', **err_context)
2364
if response[0] != 'ok':
2365
raise errors.UnexpectedSmartServerResponse(response)
2366
ok, branch_token, repo_token = response
2367
return branch_token, repo_token
2369
def lock_write(self, token=None):
2370
if not self._lock_mode:
2371
self._note_lock('w')
2372
# Lock the branch and repo in one remote call.
2373
remote_tokens = self._remote_lock_write(token)
2374
self._lock_token, self._repo_lock_token = remote_tokens
2375
if not self._lock_token:
2376
raise SmartProtocolError('Remote server did not return a token!')
2377
# Tell the self.repository object that it is locked.
2378
self.repository.lock_write(
2379
self._repo_lock_token, _skip_rpc=True)
2381
if self._real_branch is not None:
2382
self._real_branch.lock_write(token=self._lock_token)
2383
if token is not None:
2384
self._leave_lock = True
2386
self._leave_lock = False
2387
self._lock_mode = 'w'
2388
self._lock_count = 1
2389
elif self._lock_mode == 'r':
2390
raise errors.ReadOnlyTransaction
2392
if token is not None:
2393
# A token was given to lock_write, and we're relocking, so
2394
# check that the given token actually matches the one we
2396
if token != self._lock_token:
2397
raise errors.TokenMismatch(token, self._lock_token)
2398
self._lock_count += 1
2399
# Re-lock the repository too.
2400
self.repository.lock_write(self._repo_lock_token)
2401
return self._lock_token or None
2403
def _unlock(self, branch_token, repo_token):
2404
err_context = {'token': str((branch_token, repo_token))}
2405
response = self._call(
2406
'Branch.unlock', self._remote_path(), branch_token,
2407
repo_token or '', **err_context)
2408
if response == ('ok',):
2410
raise errors.UnexpectedSmartServerResponse(response)
2412
@only_raises(errors.LockNotHeld, errors.LockBroken)
2415
self._lock_count -= 1
2416
if not self._lock_count:
2417
self._clear_cached_state()
2418
mode = self._lock_mode
2419
self._lock_mode = None
2420
if self._real_branch is not None:
2421
if (not self._leave_lock and mode == 'w' and
2422
self._repo_lock_token):
2423
# If this RemoteBranch will remove the physical lock
2424
# for the repository, make sure the _real_branch
2425
# doesn't do it first. (Because the _real_branch's
2426
# repository is set to be the RemoteRepository.)
2427
self._real_branch.repository.leave_lock_in_place()
2428
self._real_branch.unlock()
2430
# Only write-locked branched need to make a remote method
2431
# call to perform the unlock.
2433
if not self._lock_token:
2434
raise AssertionError('Locked, but no token!')
2435
branch_token = self._lock_token
2436
repo_token = self._repo_lock_token
2437
self._lock_token = None
2438
self._repo_lock_token = None
2439
if not self._leave_lock:
2440
self._unlock(branch_token, repo_token)
2442
self.repository.unlock()
2444
def break_lock(self):
2446
return self._real_branch.break_lock()
2448
def leave_lock_in_place(self):
2449
if not self._lock_token:
2450
raise NotImplementedError(self.leave_lock_in_place)
2451
self._leave_lock = True
2453
def dont_leave_lock_in_place(self):
2454
if not self._lock_token:
2455
raise NotImplementedError(self.dont_leave_lock_in_place)
2456
self._leave_lock = False
2459
def get_rev_id(self, revno, history=None):
2461
return _mod_revision.NULL_REVISION
2462
last_revision_info = self.last_revision_info()
2463
ok, result = self.repository.get_rev_id_for_revno(
2464
revno, last_revision_info)
2467
missing_parent = result[1]
2468
# Either the revision named by the server is missing, or its parent
2469
# is. Call get_parent_map to determine which, so that we report a
2471
parent_map = self.repository.get_parent_map([missing_parent])
2472
if missing_parent in parent_map:
2473
missing_parent = parent_map[missing_parent]
2474
raise errors.RevisionNotPresent(missing_parent, self.repository)
2476
def _last_revision_info(self):
2477
response = self._call('Branch.last_revision_info', self._remote_path())
2478
if response[0] != 'ok':
2479
raise SmartProtocolError('unexpected response code %s' % (response,))
2480
revno = int(response[1])
2481
last_revision = response[2]
2482
return (revno, last_revision)
2484
def _gen_revision_history(self):
2485
"""See Branch._gen_revision_history()."""
2486
if self._is_stacked:
2488
return self._real_branch._gen_revision_history()
2489
response_tuple, response_handler = self._call_expecting_body(
2490
'Branch.revision_history', self._remote_path())
2491
if response_tuple[0] != 'ok':
2492
raise errors.UnexpectedSmartServerResponse(response_tuple)
2493
result = response_handler.read_body_bytes().split('\x00')
2498
def _remote_path(self):
2499
return self.bzrdir._path_for_remote_call(self._client)
2501
def _set_last_revision_descendant(self, revision_id, other_branch,
2502
allow_diverged=False, allow_overwrite_descendant=False):
2503
# This performs additional work to meet the hook contract; while its
2504
# undesirable, we have to synthesise the revno to call the hook, and
2505
# not calling the hook is worse as it means changes can't be prevented.
2506
# Having calculated this though, we can't just call into
2507
# set_last_revision_info as a simple call, because there is a set_rh
2508
# hook that some folk may still be using.
2509
old_revno, old_revid = self.last_revision_info()
2510
history = self._lefthand_history(revision_id)
2511
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2512
err_context = {'other_branch': other_branch}
2513
response = self._call('Branch.set_last_revision_ex',
2514
self._remote_path(), self._lock_token, self._repo_lock_token,
2515
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2517
self._clear_cached_state()
2518
if len(response) != 3 and response[0] != 'ok':
2519
raise errors.UnexpectedSmartServerResponse(response)
2520
new_revno, new_revision_id = response[1:]
2521
self._last_revision_info_cache = new_revno, new_revision_id
2522
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2523
if self._real_branch is not None:
2524
cache = new_revno, new_revision_id
2525
self._real_branch._last_revision_info_cache = cache
2527
def _set_last_revision(self, revision_id):
2528
old_revno, old_revid = self.last_revision_info()
2529
# This performs additional work to meet the hook contract; while its
2530
# undesirable, we have to synthesise the revno to call the hook, and
2531
# not calling the hook is worse as it means changes can't be prevented.
2532
# Having calculated this though, we can't just call into
2533
# set_last_revision_info as a simple call, because there is a set_rh
2534
# hook that some folk may still be using.
2535
history = self._lefthand_history(revision_id)
2536
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2537
self._clear_cached_state()
2538
response = self._call('Branch.set_last_revision',
2539
self._remote_path(), self._lock_token, self._repo_lock_token,
2541
if response != ('ok',):
2542
raise errors.UnexpectedSmartServerResponse(response)
2543
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2546
def set_revision_history(self, rev_history):
2547
# Send just the tip revision of the history; the server will generate
2548
# the full history from that. If the revision doesn't exist in this
2549
# branch, NoSuchRevision will be raised.
2550
if rev_history == []:
2553
rev_id = rev_history[-1]
2554
self._set_last_revision(rev_id)
2555
for hook in branch.Branch.hooks['set_rh']:
2556
hook(self, rev_history)
2557
self._cache_revision_history(rev_history)
2559
def _get_parent_location(self):
2560
medium = self._client._medium
2561
if medium._is_remote_before((1, 13)):
2562
return self._vfs_get_parent_location()
2564
response = self._call('Branch.get_parent', self._remote_path())
2565
except errors.UnknownSmartMethod:
2566
medium._remember_remote_is_before((1, 13))
2567
return self._vfs_get_parent_location()
2568
if len(response) != 1:
2569
raise errors.UnexpectedSmartServerResponse(response)
2570
parent_location = response[0]
2571
if parent_location == '':
2573
return parent_location
2575
def _vfs_get_parent_location(self):
2577
return self._real_branch._get_parent_location()
2579
def _set_parent_location(self, url):
2580
medium = self._client._medium
2581
if medium._is_remote_before((1, 15)):
2582
return self._vfs_set_parent_location(url)
2584
call_url = url or ''
2585
if type(call_url) is not str:
2586
raise AssertionError('url must be a str or None (%s)' % url)
2587
response = self._call('Branch.set_parent_location',
2588
self._remote_path(), self._lock_token, self._repo_lock_token,
2590
except errors.UnknownSmartMethod:
2591
medium._remember_remote_is_before((1, 15))
2592
return self._vfs_set_parent_location(url)
2594
raise errors.UnexpectedSmartServerResponse(response)
2596
def _vfs_set_parent_location(self, url):
2598
return self._real_branch._set_parent_location(url)
2601
def pull(self, source, overwrite=False, stop_revision=None,
2603
self._clear_cached_state_of_remote_branch_only()
2605
return self._real_branch.pull(
2606
source, overwrite=overwrite, stop_revision=stop_revision,
2607
_override_hook_target=self, **kwargs)
2610
def push(self, target, overwrite=False, stop_revision=None):
2612
return self._real_branch.push(
2613
target, overwrite=overwrite, stop_revision=stop_revision,
2614
_override_hook_source_branch=self)
2616
def is_locked(self):
2617
return self._lock_count >= 1
2620
def revision_id_to_revno(self, revision_id):
2622
return self._real_branch.revision_id_to_revno(revision_id)
2625
def set_last_revision_info(self, revno, revision_id):
2626
# XXX: These should be returned by the set_last_revision_info verb
2627
old_revno, old_revid = self.last_revision_info()
2628
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2629
revision_id = ensure_null(revision_id)
2631
response = self._call('Branch.set_last_revision_info',
2632
self._remote_path(), self._lock_token, self._repo_lock_token,
2633
str(revno), revision_id)
2634
except errors.UnknownSmartMethod:
2636
self._clear_cached_state_of_remote_branch_only()
2637
self._real_branch.set_last_revision_info(revno, revision_id)
2638
self._last_revision_info_cache = revno, revision_id
2640
if response == ('ok',):
2641
self._clear_cached_state()
2642
self._last_revision_info_cache = revno, revision_id
2643
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2644
# Update the _real_branch's cache too.
2645
if self._real_branch is not None:
2646
cache = self._last_revision_info_cache
2647
self._real_branch._last_revision_info_cache = cache
2649
raise errors.UnexpectedSmartServerResponse(response)
2652
def generate_revision_history(self, revision_id, last_rev=None,
2654
medium = self._client._medium
2655
if not medium._is_remote_before((1, 6)):
2656
# Use a smart method for 1.6 and above servers
2658
self._set_last_revision_descendant(revision_id, other_branch,
2659
allow_diverged=True, allow_overwrite_descendant=True)
2661
except errors.UnknownSmartMethod:
2662
medium._remember_remote_is_before((1, 6))
2663
self._clear_cached_state_of_remote_branch_only()
2664
self.set_revision_history(self._lefthand_history(revision_id,
2665
last_rev=last_rev,other_branch=other_branch))
2667
def set_push_location(self, location):
2669
return self._real_branch.set_push_location(location)
2672
class RemoteConfig(object):
2673
"""A Config that reads and writes from smart verbs.
2675
It is a low-level object that considers config data to be name/value pairs
2676
that may be associated with a section. Assigning meaning to the these
2677
values is done at higher levels like bzrlib.config.TreeConfig.
2680
def get_option(self, name, section=None, default=None):
2681
"""Return the value associated with a named option.
2683
:param name: The name of the value
2684
:param section: The section the option is in (if any)
2685
:param default: The value to return if the value is not set
2686
:return: The value or default value
2689
configobj = self._get_configobj()
2691
section_obj = configobj
2694
section_obj = configobj[section]
2697
return section_obj.get(name, default)
2698
except errors.UnknownSmartMethod:
2699
return self._vfs_get_option(name, section, default)
2701
def _response_to_configobj(self, response):
2702
if len(response[0]) and response[0][0] != 'ok':
2703
raise errors.UnexpectedSmartServerResponse(response)
2704
lines = response[1].read_body_bytes().splitlines()
2705
return config.ConfigObj(lines, encoding='utf-8')
2708
class RemoteBranchConfig(RemoteConfig):
2709
"""A RemoteConfig for Branches."""
2711
def __init__(self, branch):
2712
self._branch = branch
2714
def _get_configobj(self):
2715
path = self._branch._remote_path()
2716
response = self._branch._client.call_expecting_body(
2717
'Branch.get_config_file', path)
2718
return self._response_to_configobj(response)
2720
def set_option(self, value, name, section=None):
2721
"""Set the value associated with a named option.
2723
:param value: The value to set
2724
:param name: The name of the value to set
2725
:param section: The section the option is in (if any)
2727
medium = self._branch._client._medium
2728
if medium._is_remote_before((1, 14)):
2729
return self._vfs_set_option(value, name, section)
2731
path = self._branch._remote_path()
2732
response = self._branch._client.call('Branch.set_config_option',
2733
path, self._branch._lock_token, self._branch._repo_lock_token,
2734
value.encode('utf8'), name, section or '')
2735
except errors.UnknownSmartMethod:
2736
medium._remember_remote_is_before((1, 14))
2737
return self._vfs_set_option(value, name, section)
2739
raise errors.UnexpectedSmartServerResponse(response)
2741
def _real_object(self):
2742
self._branch._ensure_real()
2743
return self._branch._real_branch
2745
def _vfs_set_option(self, value, name, section=None):
2746
return self._real_object()._get_config().set_option(
2747
value, name, section)
2750
class RemoteBzrDirConfig(RemoteConfig):
2751
"""A RemoteConfig for BzrDirs."""
2753
def __init__(self, bzrdir):
2754
self._bzrdir = bzrdir
2756
def _get_configobj(self):
2757
medium = self._bzrdir._client._medium
2758
verb = 'BzrDir.get_config_file'
2759
if medium._is_remote_before((1, 15)):
2760
raise errors.UnknownSmartMethod(verb)
2761
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2762
response = self._bzrdir._call_expecting_body(
2764
return self._response_to_configobj(response)
2766
def _vfs_get_option(self, name, section, default):
2767
return self._real_object()._get_config().get_option(
2768
name, section, default)
2770
def set_option(self, value, name, section=None):
2771
"""Set the value associated with a named option.
2773
:param value: The value to set
2774
:param name: The name of the value to set
2775
:param section: The section the option is in (if any)
2777
return self._real_object()._get_config().set_option(
2778
value, name, section)
2780
def _real_object(self):
2781
self._bzrdir._ensure_real()
2782
return self._bzrdir._real_bzrdir
2786
def _extract_tar(tar, to_dir):
2787
"""Extract all the contents of a tarfile object.
2789
A replacement for extractall, which is not present in python2.4
2792
tar.extract(tarinfo, to_dir)
2795
def _translate_error(err, **context):
2796
"""Translate an ErrorFromSmartServer into a more useful error.
2798
Possible context keys:
2806
If the error from the server doesn't match a known pattern, then
2807
UnknownErrorFromSmartServer is raised.
2811
return context[name]
2812
except KeyError, key_err:
2813
mutter('Missing key %r in context %r', key_err.args[0], context)
2816
"""Get the path from the context if present, otherwise use first error
2820
return context['path']
2821
except KeyError, key_err:
2823
return err.error_args[0]
2824
except IndexError, idx_err:
2826
'Missing key %r in context %r', key_err.args[0], context)
2829
if err.error_verb == 'IncompatibleRepositories':
2830
raise errors.IncompatibleRepositories(err.error_args[0],
2831
err.error_args[1], err.error_args[2])
2832
elif err.error_verb == 'NoSuchRevision':
2833
raise NoSuchRevision(find('branch'), err.error_args[0])
2834
elif err.error_verb == 'nosuchrevision':
2835
raise NoSuchRevision(find('repository'), err.error_args[0])
2836
elif err.error_verb == 'nobranch':
2837
if len(err.error_args) >= 1:
2838
extra = err.error_args[0]
2841
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2843
elif err.error_verb == 'norepository':
2844
raise errors.NoRepositoryPresent(find('bzrdir'))
2845
elif err.error_verb == 'LockContention':
2846
raise errors.LockContention('(remote lock)')
2847
elif err.error_verb == 'UnlockableTransport':
2848
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2849
elif err.error_verb == 'LockFailed':
2850
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2851
elif err.error_verb == 'TokenMismatch':
2852
raise errors.TokenMismatch(find('token'), '(remote token)')
2853
elif err.error_verb == 'Diverged':
2854
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2855
elif err.error_verb == 'TipChangeRejected':
2856
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2857
elif err.error_verb == 'UnstackableBranchFormat':
2858
raise errors.UnstackableBranchFormat(*err.error_args)
2859
elif err.error_verb == 'UnstackableRepositoryFormat':
2860
raise errors.UnstackableRepositoryFormat(*err.error_args)
2861
elif err.error_verb == 'NotStacked':
2862
raise errors.NotStacked(branch=find('branch'))
2863
elif err.error_verb == 'PermissionDenied':
2865
if len(err.error_args) >= 2:
2866
extra = err.error_args[1]
2869
raise errors.PermissionDenied(path, extra=extra)
2870
elif err.error_verb == 'ReadError':
2872
raise errors.ReadError(path)
2873
elif err.error_verb == 'NoSuchFile':
2875
raise errors.NoSuchFile(path)
2876
elif err.error_verb == 'FileExists':
2877
raise errors.FileExists(err.error_args[0])
2878
elif err.error_verb == 'DirectoryNotEmpty':
2879
raise errors.DirectoryNotEmpty(err.error_args[0])
2880
elif err.error_verb == 'ShortReadvError':
2881
args = err.error_args
2882
raise errors.ShortReadvError(
2883
args[0], int(args[1]), int(args[2]), int(args[3]))
2884
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2885
encoding = str(err.error_args[0]) # encoding must always be a string
2886
val = err.error_args[1]
2887
start = int(err.error_args[2])
2888
end = int(err.error_args[3])
2889
reason = str(err.error_args[4]) # reason must always be a string
2890
if val.startswith('u:'):
2891
val = val[2:].decode('utf-8')
2892
elif val.startswith('s:'):
2893
val = val[2:].decode('base64')
2894
if err.error_verb == 'UnicodeDecodeError':
2895
raise UnicodeDecodeError(encoding, val, start, end, reason)
2896
elif err.error_verb == 'UnicodeEncodeError':
2897
raise UnicodeEncodeError(encoding, val, start, end, reason)
2898
elif err.error_verb == 'ReadOnlyError':
2899
raise errors.TransportNotPossible('readonly transport')
2900
raise errors.UnknownErrorFromSmartServer(err)