1
# Copyright (C) 2006, 2007, 2008, 2009 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
31
revision as _mod_revision,
34
from bzrlib.branch import BranchReferenceFormat
35
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
36
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
37
from bzrlib.errors import (
41
from bzrlib.lockable_files import LockableFiles
42
from bzrlib.smart import client, vfs, repository as smart_repo
43
from bzrlib.revision import ensure_null, NULL_REVISION
44
from bzrlib.trace import mutter, note, warning
47
class _RpcHelper(object):
48
"""Mixin class that helps with issuing RPCs."""
50
def _call(self, method, *args, **err_context):
52
return self._client.call(method, *args)
53
except errors.ErrorFromSmartServer, err:
54
self._translate_error(err, **err_context)
56
def _call_expecting_body(self, method, *args, **err_context):
58
return self._client.call_expecting_body(method, *args)
59
except errors.ErrorFromSmartServer, err:
60
self._translate_error(err, **err_context)
62
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
64
return self._client.call_with_body_bytes(method, args, body_bytes)
65
except errors.ErrorFromSmartServer, err:
66
self._translate_error(err, **err_context)
68
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
71
return self._client.call_with_body_bytes_expecting_body(
72
method, args, body_bytes)
73
except errors.ErrorFromSmartServer, err:
74
self._translate_error(err, **err_context)
77
def response_tuple_to_repo_format(response):
78
"""Convert a response tuple describing a repository format to a format."""
79
format = RemoteRepositoryFormat()
80
format._rich_root_data = (response[0] == 'yes')
81
format._supports_tree_reference = (response[1] == 'yes')
82
format._supports_external_lookups = (response[2] == 'yes')
83
format._network_name = response[3]
87
# Note: RemoteBzrDirFormat is in bzrdir.py
89
class RemoteBzrDir(BzrDir, _RpcHelper):
90
"""Control directory on a remote server, accessed via bzr:// or similar."""
92
def __init__(self, transport, format, _client=None, _force_probe=False):
93
"""Construct a RemoteBzrDir.
95
:param _client: Private parameter for testing. Disables probing and the
98
BzrDir.__init__(self, transport, format)
99
# this object holds a delegated bzrdir that uses file-level operations
100
# to talk to the other side
101
self._real_bzrdir = None
102
self._has_working_tree = None
103
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
104
# create_branch for details.
105
self._next_open_branch_result = None
108
medium = transport.get_smart_medium()
109
self._client = client._SmartClient(medium)
111
self._client = _client
117
def _probe_bzrdir(self):
118
medium = self._client._medium
119
path = self._path_for_remote_call(self._client)
120
if medium._is_remote_before((2, 1)):
124
self._rpc_open_2_1(path)
126
except errors.UnknownSmartMethod:
127
medium._remember_remote_is_before((2, 1))
130
def _rpc_open_2_1(self, path):
131
response = self._call('BzrDir.open_2.1', path)
132
if response == ('no',):
133
raise errors.NotBranchError(path=self.root_transport.base)
134
elif response[0] == 'yes':
135
if response[1] == 'yes':
136
self._has_working_tree = True
137
elif response[1] == 'no':
138
self._has_working_tree = False
140
raise errors.UnexpectedSmartServerResponse(response)
142
raise errors.UnexpectedSmartServerResponse(response)
144
def _rpc_open(self, path):
145
response = self._call('BzrDir.open', path)
146
if response not in [('yes',), ('no',)]:
147
raise errors.UnexpectedSmartServerResponse(response)
148
if response == ('no',):
149
raise errors.NotBranchError(path=self.root_transport.base)
151
def _ensure_real(self):
152
"""Ensure that there is a _real_bzrdir set.
154
Used before calls to self._real_bzrdir.
156
if not self._real_bzrdir:
157
if 'hpssvfs' in debug.debug_flags:
159
warning('VFS BzrDir access triggered\n%s',
160
''.join(traceback.format_stack()))
161
self._real_bzrdir = BzrDir.open_from_transport(
162
self.root_transport, _server_formats=False)
163
self._format._network_name = \
164
self._real_bzrdir._format.network_name()
166
def _translate_error(self, err, **context):
167
_translate_error(err, bzrdir=self, **context)
169
def break_lock(self):
170
# Prevent aliasing problems in the next_open_branch_result cache.
171
# See create_branch for rationale.
172
self._next_open_branch_result = None
173
return BzrDir.break_lock(self)
175
def _vfs_cloning_metadir(self, require_stacking=False):
177
return self._real_bzrdir.cloning_metadir(
178
require_stacking=require_stacking)
180
def cloning_metadir(self, require_stacking=False):
181
medium = self._client._medium
182
if medium._is_remote_before((1, 13)):
183
return self._vfs_cloning_metadir(require_stacking=require_stacking)
184
verb = 'BzrDir.cloning_metadir'
189
path = self._path_for_remote_call(self._client)
191
response = self._call(verb, path, stacking)
192
except errors.UnknownSmartMethod:
193
medium._remember_remote_is_before((1, 13))
194
return self._vfs_cloning_metadir(require_stacking=require_stacking)
195
except errors.UnknownErrorFromSmartServer, err:
196
if err.error_tuple != ('BranchReference',):
198
# We need to resolve the branch reference to determine the
199
# cloning_metadir. This causes unnecessary RPCs to open the
200
# referenced branch (and bzrdir, etc) but only when the caller
201
# didn't already resolve the branch reference.
202
referenced_branch = self.open_branch()
203
return referenced_branch.bzrdir.cloning_metadir()
204
if len(response) != 3:
205
raise errors.UnexpectedSmartServerResponse(response)
206
control_name, repo_name, branch_info = response
207
if len(branch_info) != 2:
208
raise errors.UnexpectedSmartServerResponse(response)
209
branch_ref, branch_name = branch_info
210
format = bzrdir.network_format_registry.get(control_name)
212
format.repository_format = repository.network_format_registry.get(
214
if branch_ref == 'ref':
215
# XXX: we need possible_transports here to avoid reopening the
216
# connection to the referenced location
217
ref_bzrdir = BzrDir.open(branch_name)
218
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
219
format.set_branch_format(branch_format)
220
elif branch_ref == 'branch':
222
format.set_branch_format(
223
branch.network_format_registry.get(branch_name))
225
raise errors.UnexpectedSmartServerResponse(response)
228
def create_repository(self, shared=False):
229
# as per meta1 formats - just delegate to the format object which may
231
result = self._format.repository_format.initialize(self, shared)
232
if not isinstance(result, RemoteRepository):
233
return self.open_repository()
237
def destroy_repository(self):
238
"""See BzrDir.destroy_repository"""
240
self._real_bzrdir.destroy_repository()
242
def create_branch(self):
243
# as per meta1 formats - just delegate to the format object which may
245
real_branch = self._format.get_branch_format().initialize(self)
246
if not isinstance(real_branch, RemoteBranch):
247
result = RemoteBranch(self, self.find_repository(), real_branch)
250
# BzrDir.clone_on_transport() uses the result of create_branch but does
251
# not return it to its callers; we save approximately 8% of our round
252
# trips by handing the branch we created back to the first caller to
253
# open_branch rather than probing anew. Long term we need a API in
254
# bzrdir that doesn't discard result objects (like result_branch).
256
self._next_open_branch_result = result
259
def destroy_branch(self):
260
"""See BzrDir.destroy_branch"""
262
self._real_bzrdir.destroy_branch()
263
self._next_open_branch_result = None
265
def create_workingtree(self, revision_id=None, from_branch=None):
266
raise errors.NotLocalUrl(self.transport.base)
268
def find_branch_format(self):
269
"""Find the branch 'format' for this bzrdir.
271
This might be a synthetic object for e.g. RemoteBranch and SVN.
273
b = self.open_branch()
276
def get_branch_reference(self):
277
"""See BzrDir.get_branch_reference()."""
278
response = self._get_branch_reference()
279
if response[0] == 'ref':
284
def _get_branch_reference(self):
285
path = self._path_for_remote_call(self._client)
286
medium = self._client._medium
287
if not medium._is_remote_before((1, 13)):
289
response = self._call('BzrDir.open_branchV2', path)
290
if response[0] not in ('ref', 'branch'):
291
raise errors.UnexpectedSmartServerResponse(response)
293
except errors.UnknownSmartMethod:
294
medium._remember_remote_is_before((1, 13))
295
response = self._call('BzrDir.open_branch', path)
296
if response[0] != 'ok':
297
raise errors.UnexpectedSmartServerResponse(response)
298
if response[1] != '':
299
return ('ref', response[1])
301
return ('branch', '')
303
def _get_tree_branch(self):
304
"""See BzrDir._get_tree_branch()."""
305
return None, self.open_branch()
307
def open_branch(self, _unsupported=False, ignore_fallbacks=False):
309
raise NotImplementedError('unsupported flag support not implemented yet.')
310
if self._next_open_branch_result is not None:
311
# See create_branch for details.
312
result = self._next_open_branch_result
313
self._next_open_branch_result = None
315
response = self._get_branch_reference()
316
if response[0] == 'ref':
317
# a branch reference, use the existing BranchReference logic.
318
format = BranchReferenceFormat()
319
return format.open(self, _found=True, location=response[1],
320
ignore_fallbacks=ignore_fallbacks)
321
branch_format_name = response[1]
322
if not branch_format_name:
323
branch_format_name = None
324
format = RemoteBranchFormat(network_name=branch_format_name)
325
return RemoteBranch(self, self.find_repository(), format=format,
326
setup_stacking=not ignore_fallbacks)
328
def _open_repo_v1(self, path):
329
verb = 'BzrDir.find_repository'
330
response = self._call(verb, path)
331
if response[0] != 'ok':
332
raise errors.UnexpectedSmartServerResponse(response)
333
# servers that only support the v1 method don't support external
336
repo = self._real_bzrdir.open_repository()
337
response = response + ('no', repo._format.network_name())
338
return response, repo
340
def _open_repo_v2(self, path):
341
verb = 'BzrDir.find_repositoryV2'
342
response = self._call(verb, path)
343
if response[0] != 'ok':
344
raise errors.UnexpectedSmartServerResponse(response)
346
repo = self._real_bzrdir.open_repository()
347
response = response + (repo._format.network_name(),)
348
return response, repo
350
def _open_repo_v3(self, path):
351
verb = 'BzrDir.find_repositoryV3'
352
medium = self._client._medium
353
if medium._is_remote_before((1, 13)):
354
raise errors.UnknownSmartMethod(verb)
356
response = self._call(verb, path)
357
except errors.UnknownSmartMethod:
358
medium._remember_remote_is_before((1, 13))
360
if response[0] != 'ok':
361
raise errors.UnexpectedSmartServerResponse(response)
362
return response, None
364
def open_repository(self):
365
path = self._path_for_remote_call(self._client)
367
for probe in [self._open_repo_v3, self._open_repo_v2,
370
response, real_repo = probe(path)
372
except errors.UnknownSmartMethod:
375
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
376
if response[0] != 'ok':
377
raise errors.UnexpectedSmartServerResponse(response)
378
if len(response) != 6:
379
raise SmartProtocolError('incorrect response length %s' % (response,))
380
if response[1] == '':
381
# repo is at this dir.
382
format = response_tuple_to_repo_format(response[2:])
383
# Used to support creating a real format instance when needed.
384
format._creating_bzrdir = self
385
remote_repo = RemoteRepository(self, format)
386
format._creating_repo = remote_repo
387
if real_repo is not None:
388
remote_repo._set_real_repository(real_repo)
391
raise errors.NoRepositoryPresent(self)
393
def has_workingtree(self):
394
if self._has_working_tree is None:
396
self._has_working_tree = self._real_bzrdir.has_workingtree()
397
return self._has_working_tree
399
def open_workingtree(self, recommend_upgrade=True):
400
if self.has_workingtree():
401
raise errors.NotLocalUrl(self.root_transport)
403
raise errors.NoWorkingTree(self.root_transport.base)
405
def _path_for_remote_call(self, client):
406
"""Return the path to be used for this bzrdir in a remote call."""
407
return client.remote_path_from_transport(self.root_transport)
409
def get_branch_transport(self, branch_format):
411
return self._real_bzrdir.get_branch_transport(branch_format)
413
def get_repository_transport(self, repository_format):
415
return self._real_bzrdir.get_repository_transport(repository_format)
417
def get_workingtree_transport(self, workingtree_format):
419
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
421
def can_convert_format(self):
422
"""Upgrading of remote bzrdirs is not supported yet."""
425
def needs_format_conversion(self, format=None):
426
"""Upgrading of remote bzrdirs is not supported yet."""
428
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
429
% 'needs_format_conversion(format=None)')
432
def clone(self, url, revision_id=None, force_new_repo=False,
433
preserve_stacking=False):
435
return self._real_bzrdir.clone(url, revision_id=revision_id,
436
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
438
def _get_config(self):
439
return RemoteBzrDirConfig(self)
442
class RemoteRepositoryFormat(repository.RepositoryFormat):
443
"""Format for repositories accessed over a _SmartClient.
445
Instances of this repository are represented by RemoteRepository
448
The RemoteRepositoryFormat is parameterized during construction
449
to reflect the capabilities of the real, remote format. Specifically
450
the attributes rich_root_data and supports_tree_reference are set
451
on a per instance basis, and are not set (and should not be) at
454
:ivar _custom_format: If set, a specific concrete repository format that
455
will be used when initializing a repository with this
456
RemoteRepositoryFormat.
457
:ivar _creating_repo: If set, the repository object that this
458
RemoteRepositoryFormat was created for: it can be called into
459
to obtain data like the network name.
462
_matchingbzrdir = RemoteBzrDirFormat()
465
repository.RepositoryFormat.__init__(self)
466
self._custom_format = None
467
self._network_name = None
468
self._creating_bzrdir = None
469
self._supports_chks = None
470
self._supports_external_lookups = None
471
self._supports_tree_reference = None
472
self._rich_root_data = None
475
return "%s(_network_name=%r)" % (self.__class__.__name__,
479
def fast_deltas(self):
481
return self._custom_format.fast_deltas
484
def rich_root_data(self):
485
if self._rich_root_data is None:
487
self._rich_root_data = self._custom_format.rich_root_data
488
return self._rich_root_data
491
def supports_chks(self):
492
if self._supports_chks is None:
494
self._supports_chks = self._custom_format.supports_chks
495
return self._supports_chks
498
def supports_external_lookups(self):
499
if self._supports_external_lookups is None:
501
self._supports_external_lookups = \
502
self._custom_format.supports_external_lookups
503
return self._supports_external_lookups
506
def supports_tree_reference(self):
507
if self._supports_tree_reference is None:
509
self._supports_tree_reference = \
510
self._custom_format.supports_tree_reference
511
return self._supports_tree_reference
513
def _vfs_initialize(self, a_bzrdir, shared):
514
"""Helper for common code in initialize."""
515
if self._custom_format:
516
# Custom format requested
517
result = self._custom_format.initialize(a_bzrdir, shared=shared)
518
elif self._creating_bzrdir is not None:
519
# Use the format that the repository we were created to back
521
prior_repo = self._creating_bzrdir.open_repository()
522
prior_repo._ensure_real()
523
result = prior_repo._real_repository._format.initialize(
524
a_bzrdir, shared=shared)
526
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
527
# support remote initialization.
528
# We delegate to a real object at this point (as RemoteBzrDir
529
# delegate to the repository format which would lead to infinite
530
# recursion if we just called a_bzrdir.create_repository.
531
a_bzrdir._ensure_real()
532
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
533
if not isinstance(result, RemoteRepository):
534
return self.open(a_bzrdir)
538
def initialize(self, a_bzrdir, shared=False):
539
# Being asked to create on a non RemoteBzrDir:
540
if not isinstance(a_bzrdir, RemoteBzrDir):
541
return self._vfs_initialize(a_bzrdir, shared)
542
medium = a_bzrdir._client._medium
543
if medium._is_remote_before((1, 13)):
544
return self._vfs_initialize(a_bzrdir, shared)
545
# Creating on a remote bzr dir.
546
# 1) get the network name to use.
547
if self._custom_format:
548
network_name = self._custom_format.network_name()
549
elif self._network_name:
550
network_name = self._network_name
552
# Select the current bzrlib default and ask for that.
553
reference_bzrdir_format = bzrdir.format_registry.get('default')()
554
reference_format = reference_bzrdir_format.repository_format
555
network_name = reference_format.network_name()
556
# 2) try direct creation via RPC
557
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
558
verb = 'BzrDir.create_repository'
564
response = a_bzrdir._call(verb, path, network_name, shared_str)
565
except errors.UnknownSmartMethod:
566
# Fallback - use vfs methods
567
medium._remember_remote_is_before((1, 13))
568
return self._vfs_initialize(a_bzrdir, shared)
570
# Turn the response into a RemoteRepository object.
571
format = response_tuple_to_repo_format(response[1:])
572
# Used to support creating a real format instance when needed.
573
format._creating_bzrdir = a_bzrdir
574
remote_repo = RemoteRepository(a_bzrdir, format)
575
format._creating_repo = remote_repo
578
def open(self, a_bzrdir):
579
if not isinstance(a_bzrdir, RemoteBzrDir):
580
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
581
return a_bzrdir.open_repository()
583
def _ensure_real(self):
584
if self._custom_format is None:
585
self._custom_format = repository.network_format_registry.get(
589
def _fetch_order(self):
591
return self._custom_format._fetch_order
594
def _fetch_uses_deltas(self):
596
return self._custom_format._fetch_uses_deltas
599
def _fetch_reconcile(self):
601
return self._custom_format._fetch_reconcile
603
def get_format_description(self):
605
return 'Remote: ' + self._custom_format.get_format_description()
607
def __eq__(self, other):
608
return self.__class__ is other.__class__
610
def network_name(self):
611
if self._network_name:
612
return self._network_name
613
self._creating_repo._ensure_real()
614
return self._creating_repo._real_repository._format.network_name()
617
def pack_compresses(self):
619
return self._custom_format.pack_compresses
622
def _serializer(self):
624
return self._custom_format._serializer
627
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
628
"""Repository accessed over rpc.
630
For the moment most operations are performed using local transport-backed
634
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
635
"""Create a RemoteRepository instance.
637
:param remote_bzrdir: The bzrdir hosting this repository.
638
:param format: The RemoteFormat object to use.
639
:param real_repository: If not None, a local implementation of the
640
repository logic for the repository, usually accessing the data
642
:param _client: Private testing parameter - override the smart client
643
to be used by the repository.
646
self._real_repository = real_repository
648
self._real_repository = None
649
self.bzrdir = remote_bzrdir
651
self._client = remote_bzrdir._client
653
self._client = _client
654
self._format = format
655
self._lock_mode = None
656
self._lock_token = None
658
self._leave_lock = False
659
# Cache of revision parents; misses are cached during read locks, and
660
# write locks when no _real_repository has been set.
661
self._unstacked_provider = graph.CachingParentsProvider(
662
get_parent_map=self._get_parent_map_rpc)
663
self._unstacked_provider.disable_cache()
665
# These depend on the actual remote format, so force them off for
666
# maximum compatibility. XXX: In future these should depend on the
667
# remote repository instance, but this is irrelevant until we perform
668
# reconcile via an RPC call.
669
self._reconcile_does_inventory_gc = False
670
self._reconcile_fixes_text_parents = False
671
self._reconcile_backsup_inventory = False
672
self.base = self.bzrdir.transport.base
673
# Additional places to query for data.
674
self._fallback_repositories = []
677
return "%s(%s)" % (self.__class__.__name__, self.base)
681
def abort_write_group(self, suppress_errors=False):
682
"""Complete a write group on the decorated repository.
684
Smart methods perform operations in a single step so this API
685
is not really applicable except as a compatibility thunk
686
for older plugins that don't use e.g. the CommitBuilder
689
:param suppress_errors: see Repository.abort_write_group.
692
return self._real_repository.abort_write_group(
693
suppress_errors=suppress_errors)
697
"""Decorate the real repository for now.
699
In the long term a full blown network facility is needed to avoid
700
creating a real repository object locally.
703
return self._real_repository.chk_bytes
705
def commit_write_group(self):
706
"""Complete a write group on the decorated repository.
708
Smart methods perform operations in a single step so this API
709
is not really applicable except as a compatibility thunk
710
for older plugins that don't use e.g. the CommitBuilder
714
return self._real_repository.commit_write_group()
716
def resume_write_group(self, tokens):
718
return self._real_repository.resume_write_group(tokens)
720
def suspend_write_group(self):
722
return self._real_repository.suspend_write_group()
724
def get_missing_parent_inventories(self, check_for_missing_texts=True):
726
return self._real_repository.get_missing_parent_inventories(
727
check_for_missing_texts=check_for_missing_texts)
729
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
731
return self._real_repository.get_rev_id_for_revno(
734
def get_rev_id_for_revno(self, revno, known_pair):
735
"""See Repository.get_rev_id_for_revno."""
736
path = self.bzrdir._path_for_remote_call(self._client)
738
if self._client._medium._is_remote_before((1, 17)):
739
return self._get_rev_id_for_revno_vfs(revno, known_pair)
740
response = self._call(
741
'Repository.get_rev_id_for_revno', path, revno, known_pair)
742
except errors.UnknownSmartMethod:
743
self._client._medium._remember_remote_is_before((1, 17))
744
return self._get_rev_id_for_revno_vfs(revno, known_pair)
745
if response[0] == 'ok':
746
return True, response[1]
747
elif response[0] == 'history-incomplete':
748
known_pair = response[1:3]
749
for fallback in self._fallback_repositories:
750
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
755
# Not found in any fallbacks
756
return False, known_pair
758
raise errors.UnexpectedSmartServerResponse(response)
760
def _ensure_real(self):
761
"""Ensure that there is a _real_repository set.
763
Used before calls to self._real_repository.
765
Note that _ensure_real causes many roundtrips to the server which are
766
not desirable, and prevents the use of smart one-roundtrip RPC's to
767
perform complex operations (such as accessing parent data, streaming
768
revisions etc). Adding calls to _ensure_real should only be done when
769
bringing up new functionality, adding fallbacks for smart methods that
770
require a fallback path, and never to replace an existing smart method
771
invocation. If in doubt chat to the bzr network team.
773
if self._real_repository is None:
774
if 'hpssvfs' in debug.debug_flags:
776
warning('VFS Repository access triggered\n%s',
777
''.join(traceback.format_stack()))
778
self._unstacked_provider.missing_keys.clear()
779
self.bzrdir._ensure_real()
780
self._set_real_repository(
781
self.bzrdir._real_bzrdir.open_repository())
783
def _translate_error(self, err, **context):
784
self.bzrdir._translate_error(err, repository=self, **context)
786
def find_text_key_references(self):
787
"""Find the text key references within the repository.
789
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
790
revision_ids. Each altered file-ids has the exact revision_ids that
791
altered it listed explicitly.
792
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
793
to whether they were referred to by the inventory of the
794
revision_id that they contain. The inventory texts from all present
795
revision ids are assessed to generate this report.
798
return self._real_repository.find_text_key_references()
800
def _generate_text_key_index(self):
801
"""Generate a new text key index for the repository.
803
This is an expensive function that will take considerable time to run.
805
:return: A dict mapping (file_id, revision_id) tuples to a list of
806
parents, also (file_id, revision_id) tuples.
809
return self._real_repository._generate_text_key_index()
811
def _get_revision_graph(self, revision_id):
812
"""Private method for using with old (< 1.2) servers to fallback."""
813
if revision_id is None:
815
elif revision.is_null(revision_id):
818
path = self.bzrdir._path_for_remote_call(self._client)
819
response = self._call_expecting_body(
820
'Repository.get_revision_graph', path, revision_id)
821
response_tuple, response_handler = response
822
if response_tuple[0] != 'ok':
823
raise errors.UnexpectedSmartServerResponse(response_tuple)
824
coded = response_handler.read_body_bytes()
826
# no revisions in this repository!
828
lines = coded.split('\n')
831
d = tuple(line.split())
832
revision_graph[d[0]] = d[1:]
834
return revision_graph
837
"""See Repository._get_sink()."""
838
return RemoteStreamSink(self)
840
def _get_source(self, to_format):
841
"""Return a source for streaming from this repository."""
842
return RemoteStreamSource(self, to_format)
845
def has_revision(self, revision_id):
846
"""True if this repository has a copy of the revision."""
847
# Copy of bzrlib.repository.Repository.has_revision
848
return revision_id in self.has_revisions((revision_id,))
851
def has_revisions(self, revision_ids):
852
"""Probe to find out the presence of multiple revisions.
854
:param revision_ids: An iterable of revision_ids.
855
:return: A set of the revision_ids that were present.
857
# Copy of bzrlib.repository.Repository.has_revisions
858
parent_map = self.get_parent_map(revision_ids)
859
result = set(parent_map)
860
if _mod_revision.NULL_REVISION in revision_ids:
861
result.add(_mod_revision.NULL_REVISION)
864
def _has_same_fallbacks(self, other_repo):
865
"""Returns true if the repositories have the same fallbacks."""
866
# XXX: copied from Repository; it should be unified into a base class
867
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
868
my_fb = self._fallback_repositories
869
other_fb = other_repo._fallback_repositories
870
if len(my_fb) != len(other_fb):
872
for f, g in zip(my_fb, other_fb):
873
if not f.has_same_location(g):
877
def has_same_location(self, other):
878
# TODO: Move to RepositoryBase and unify with the regular Repository
879
# one; unfortunately the tests rely on slightly different behaviour at
880
# present -- mbp 20090710
881
return (self.__class__ is other.__class__ and
882
self.bzrdir.transport.base == other.bzrdir.transport.base)
884
def get_graph(self, other_repository=None):
885
"""Return the graph for this repository format"""
886
parents_provider = self._make_parents_provider(other_repository)
887
return graph.Graph(parents_provider)
889
def gather_stats(self, revid=None, committers=None):
890
"""See Repository.gather_stats()."""
891
path = self.bzrdir._path_for_remote_call(self._client)
892
# revid can be None to indicate no revisions, not just NULL_REVISION
893
if revid is None or revision.is_null(revid):
897
if committers is None or not committers:
898
fmt_committers = 'no'
900
fmt_committers = 'yes'
901
response_tuple, response_handler = self._call_expecting_body(
902
'Repository.gather_stats', path, fmt_revid, fmt_committers)
903
if response_tuple[0] != 'ok':
904
raise errors.UnexpectedSmartServerResponse(response_tuple)
906
body = response_handler.read_body_bytes()
908
for line in body.split('\n'):
911
key, val_text = line.split(':')
912
if key in ('revisions', 'size', 'committers'):
913
result[key] = int(val_text)
914
elif key in ('firstrev', 'latestrev'):
915
values = val_text.split(' ')[1:]
916
result[key] = (float(values[0]), long(values[1]))
920
def find_branches(self, using=False):
921
"""See Repository.find_branches()."""
922
# should be an API call to the server.
924
return self._real_repository.find_branches(using=using)
926
def get_physical_lock_status(self):
927
"""See Repository.get_physical_lock_status()."""
928
# should be an API call to the server.
930
return self._real_repository.get_physical_lock_status()
932
def is_in_write_group(self):
933
"""Return True if there is an open write group.
935
write groups are only applicable locally for the smart server..
937
if self._real_repository:
938
return self._real_repository.is_in_write_group()
941
return self._lock_count >= 1
944
"""See Repository.is_shared()."""
945
path = self.bzrdir._path_for_remote_call(self._client)
946
response = self._call('Repository.is_shared', path)
947
if response[0] not in ('yes', 'no'):
948
raise SmartProtocolError('unexpected response code %s' % (response,))
949
return response[0] == 'yes'
951
def is_write_locked(self):
952
return self._lock_mode == 'w'
954
def _warn_if_deprecated(self, branch=None):
955
# If we have a real repository, the check will be done there, if we
956
# don't the check will be done remotely.
960
# wrong eventually - want a local lock cache context
961
if not self._lock_mode:
963
self._lock_mode = 'r'
965
self._unstacked_provider.enable_cache(cache_misses=True)
966
if self._real_repository is not None:
967
self._real_repository.lock_read()
968
for repo in self._fallback_repositories:
971
self._lock_count += 1
973
def _remote_lock_write(self, token):
974
path = self.bzrdir._path_for_remote_call(self._client)
977
err_context = {'token': token}
978
response = self._call('Repository.lock_write', path, token,
980
if response[0] == 'ok':
984
raise errors.UnexpectedSmartServerResponse(response)
986
def lock_write(self, token=None, _skip_rpc=False):
987
if not self._lock_mode:
990
if self._lock_token is not None:
991
if token != self._lock_token:
992
raise errors.TokenMismatch(token, self._lock_token)
993
self._lock_token = token
995
self._lock_token = self._remote_lock_write(token)
996
# if self._lock_token is None, then this is something like packs or
997
# svn where we don't get to lock the repo, or a weave style repository
998
# where we cannot lock it over the wire and attempts to do so will
1000
if self._real_repository is not None:
1001
self._real_repository.lock_write(token=self._lock_token)
1002
if token is not None:
1003
self._leave_lock = True
1005
self._leave_lock = False
1006
self._lock_mode = 'w'
1007
self._lock_count = 1
1008
cache_misses = self._real_repository is None
1009
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1010
for repo in self._fallback_repositories:
1011
# Writes don't affect fallback repos
1013
elif self._lock_mode == 'r':
1014
raise errors.ReadOnlyError(self)
1016
self._lock_count += 1
1017
return self._lock_token or None
1019
def leave_lock_in_place(self):
1020
if not self._lock_token:
1021
raise NotImplementedError(self.leave_lock_in_place)
1022
self._leave_lock = True
1024
def dont_leave_lock_in_place(self):
1025
if not self._lock_token:
1026
raise NotImplementedError(self.dont_leave_lock_in_place)
1027
self._leave_lock = False
1029
def _set_real_repository(self, repository):
1030
"""Set the _real_repository for this repository.
1032
:param repository: The repository to fallback to for non-hpss
1033
implemented operations.
1035
if self._real_repository is not None:
1036
# Replacing an already set real repository.
1037
# We cannot do this [currently] if the repository is locked -
1038
# synchronised state might be lost.
1039
if self.is_locked():
1040
raise AssertionError('_real_repository is already set')
1041
if isinstance(repository, RemoteRepository):
1042
raise AssertionError()
1043
self._real_repository = repository
1044
# three code paths happen here:
1045
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1046
# up stacking. In this case self._fallback_repositories is [], and the
1047
# real repo is already setup. Preserve the real repo and
1048
# RemoteRepository.add_fallback_repository will avoid adding
1050
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1051
# ensure_real is triggered from a branch, the real repository to
1052
# set already has a matching list with separate instances, but
1053
# as they are also RemoteRepositories we don't worry about making the
1054
# lists be identical.
1055
# 3) new servers, RemoteRepository.ensure_real is triggered before
1056
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1057
# and need to populate it.
1058
if (self._fallback_repositories and
1059
len(self._real_repository._fallback_repositories) !=
1060
len(self._fallback_repositories)):
1061
if len(self._real_repository._fallback_repositories):
1062
raise AssertionError(
1063
"cannot cleanly remove existing _fallback_repositories")
1064
for fb in self._fallback_repositories:
1065
self._real_repository.add_fallback_repository(fb)
1066
if self._lock_mode == 'w':
1067
# if we are already locked, the real repository must be able to
1068
# acquire the lock with our token.
1069
self._real_repository.lock_write(self._lock_token)
1070
elif self._lock_mode == 'r':
1071
self._real_repository.lock_read()
1073
def start_write_group(self):
1074
"""Start a write group on the decorated repository.
1076
Smart methods perform operations in a single step so this API
1077
is not really applicable except as a compatibility thunk
1078
for older plugins that don't use e.g. the CommitBuilder
1082
return self._real_repository.start_write_group()
1084
def _unlock(self, token):
1085
path = self.bzrdir._path_for_remote_call(self._client)
1087
# with no token the remote repository is not persistently locked.
1089
err_context = {'token': token}
1090
response = self._call('Repository.unlock', path, token,
1092
if response == ('ok',):
1095
raise errors.UnexpectedSmartServerResponse(response)
1097
@only_raises(errors.LockNotHeld, errors.LockBroken)
1099
if not self._lock_count:
1100
return lock.cant_unlock_not_held(self)
1101
self._lock_count -= 1
1102
if self._lock_count > 0:
1104
self._unstacked_provider.disable_cache()
1105
old_mode = self._lock_mode
1106
self._lock_mode = None
1108
# The real repository is responsible at present for raising an
1109
# exception if it's in an unfinished write group. However, it
1110
# normally will *not* actually remove the lock from disk - that's
1111
# done by the server on receiving the Repository.unlock call.
1112
# This is just to let the _real_repository stay up to date.
1113
if self._real_repository is not None:
1114
self._real_repository.unlock()
1116
# The rpc-level lock should be released even if there was a
1117
# problem releasing the vfs-based lock.
1119
# Only write-locked repositories need to make a remote method
1120
# call to perform the unlock.
1121
old_token = self._lock_token
1122
self._lock_token = None
1123
if not self._leave_lock:
1124
self._unlock(old_token)
1125
# Fallbacks are always 'lock_read()' so we don't pay attention to
1127
for repo in self._fallback_repositories:
1130
def break_lock(self):
1131
# should hand off to the network
1133
return self._real_repository.break_lock()
1135
def _get_tarball(self, compression):
1136
"""Return a TemporaryFile containing a repository tarball.
1138
Returns None if the server does not support sending tarballs.
1141
path = self.bzrdir._path_for_remote_call(self._client)
1143
response, protocol = self._call_expecting_body(
1144
'Repository.tarball', path, compression)
1145
except errors.UnknownSmartMethod:
1146
protocol.cancel_read_body()
1148
if response[0] == 'ok':
1149
# Extract the tarball and return it
1150
t = tempfile.NamedTemporaryFile()
1151
# TODO: rpc layer should read directly into it...
1152
t.write(protocol.read_body_bytes())
1155
raise errors.UnexpectedSmartServerResponse(response)
1157
def sprout(self, to_bzrdir, revision_id=None):
1158
# TODO: Option to control what format is created?
1160
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1162
dest_repo.fetch(self, revision_id=revision_id)
1165
### These methods are just thin shims to the VFS object for now.
1167
def revision_tree(self, revision_id):
1169
return self._real_repository.revision_tree(revision_id)
1171
def get_serializer_format(self):
1173
return self._real_repository.get_serializer_format()
1175
def get_commit_builder(self, branch, parents, config, timestamp=None,
1176
timezone=None, committer=None, revprops=None,
1178
# FIXME: It ought to be possible to call this without immediately
1179
# triggering _ensure_real. For now it's the easiest thing to do.
1181
real_repo = self._real_repository
1182
builder = real_repo.get_commit_builder(branch, parents,
1183
config, timestamp=timestamp, timezone=timezone,
1184
committer=committer, revprops=revprops, revision_id=revision_id)
1187
def add_fallback_repository(self, repository):
1188
"""Add a repository to use for looking up data not held locally.
1190
:param repository: A repository.
1192
if not self._format.supports_external_lookups:
1193
raise errors.UnstackableRepositoryFormat(
1194
self._format.network_name(), self.base)
1195
# We need to accumulate additional repositories here, to pass them in
1198
if self.is_locked():
1199
# We will call fallback.unlock() when we transition to the unlocked
1200
# state, so always add a lock here. If a caller passes us a locked
1201
# repository, they are responsible for unlocking it later.
1202
repository.lock_read()
1203
self._fallback_repositories.append(repository)
1204
# If self._real_repository was parameterised already (e.g. because a
1205
# _real_branch had its get_stacked_on_url method called), then the
1206
# repository to be added may already be in the _real_repositories list.
1207
if self._real_repository is not None:
1208
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1209
self._real_repository._fallback_repositories]
1210
if repository.bzrdir.root_transport.base not in fallback_locations:
1211
self._real_repository.add_fallback_repository(repository)
1213
def add_inventory(self, revid, inv, parents):
1215
return self._real_repository.add_inventory(revid, inv, parents)
1217
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1220
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1221
delta, new_revision_id, parents)
1223
def add_revision(self, rev_id, rev, inv=None, config=None):
1225
return self._real_repository.add_revision(
1226
rev_id, rev, inv=inv, config=config)
1229
def get_inventory(self, revision_id):
1231
return self._real_repository.get_inventory(revision_id)
1233
def iter_inventories(self, revision_ids, ordering=None):
1235
return self._real_repository.iter_inventories(revision_ids, ordering)
1238
def get_revision(self, revision_id):
1240
return self._real_repository.get_revision(revision_id)
1242
def get_transaction(self):
1244
return self._real_repository.get_transaction()
1247
def clone(self, a_bzrdir, revision_id=None):
1249
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1251
def make_working_trees(self):
1252
"""See Repository.make_working_trees"""
1254
return self._real_repository.make_working_trees()
1256
def refresh_data(self):
1257
"""Re-read any data needed to to synchronise with disk.
1259
This method is intended to be called after another repository instance
1260
(such as one used by a smart server) has inserted data into the
1261
repository. It may not be called during a write group, but may be
1262
called at any other time.
1264
if self.is_in_write_group():
1265
raise errors.InternalBzrError(
1266
"May not refresh_data while in a write group.")
1267
if self._real_repository is not None:
1268
self._real_repository.refresh_data()
1270
def revision_ids_to_search_result(self, result_set):
1271
"""Convert a set of revision ids to a graph SearchResult."""
1272
result_parents = set()
1273
for parents in self.get_graph().get_parent_map(
1274
result_set).itervalues():
1275
result_parents.update(parents)
1276
included_keys = result_set.intersection(result_parents)
1277
start_keys = result_set.difference(included_keys)
1278
exclude_keys = result_parents.difference(result_set)
1279
result = graph.SearchResult(start_keys, exclude_keys,
1280
len(result_set), result_set)
1284
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1285
"""Return the revision ids that other has that this does not.
1287
These are returned in topological order.
1289
revision_id: only return revision ids included by revision_id.
1291
return repository.InterRepository.get(
1292
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1294
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1296
# No base implementation to use as RemoteRepository is not a subclass
1297
# of Repository; so this is a copy of Repository.fetch().
1298
if fetch_spec is not None and revision_id is not None:
1299
raise AssertionError(
1300
"fetch_spec and revision_id are mutually exclusive.")
1301
if self.is_in_write_group():
1302
raise errors.InternalBzrError(
1303
"May not fetch while in a write group.")
1304
# fast path same-url fetch operations
1305
if (self.has_same_location(source)
1306
and fetch_spec is None
1307
and self._has_same_fallbacks(source)):
1308
# check that last_revision is in 'from' and then return a
1310
if (revision_id is not None and
1311
not revision.is_null(revision_id)):
1312
self.get_revision(revision_id)
1314
# if there is no specific appropriate InterRepository, this will get
1315
# the InterRepository base class, which raises an
1316
# IncompatibleRepositories when asked to fetch.
1317
inter = repository.InterRepository.get(source, self)
1318
return inter.fetch(revision_id=revision_id, pb=pb,
1319
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1321
def create_bundle(self, target, base, fileobj, format=None):
1323
self._real_repository.create_bundle(target, base, fileobj, format)
1326
def get_ancestry(self, revision_id, topo_sorted=True):
1328
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1330
def fileids_altered_by_revision_ids(self, revision_ids):
1332
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1334
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1336
return self._real_repository._get_versioned_file_checker(
1337
revisions, revision_versions_cache)
1339
def iter_files_bytes(self, desired_files):
1340
"""See Repository.iter_file_bytes.
1343
return self._real_repository.iter_files_bytes(desired_files)
1345
def get_parent_map(self, revision_ids):
1346
"""See bzrlib.Graph.get_parent_map()."""
1347
return self._make_parents_provider().get_parent_map(revision_ids)
1349
def _get_parent_map_rpc(self, keys):
1350
"""Helper for get_parent_map that performs the RPC."""
1351
medium = self._client._medium
1352
if medium._is_remote_before((1, 2)):
1353
# We already found out that the server can't understand
1354
# Repository.get_parent_map requests, so just fetch the whole
1357
# Note that this reads the whole graph, when only some keys are
1358
# wanted. On this old server there's no way (?) to get them all
1359
# in one go, and the user probably will have seen a warning about
1360
# the server being old anyhow.
1361
rg = self._get_revision_graph(None)
1362
# There is an API discrepancy between get_parent_map and
1363
# get_revision_graph. Specifically, a "key:()" pair in
1364
# get_revision_graph just means a node has no parents. For
1365
# "get_parent_map" it means the node is a ghost. So fix up the
1366
# graph to correct this.
1367
# https://bugs.launchpad.net/bzr/+bug/214894
1368
# There is one other "bug" which is that ghosts in
1369
# get_revision_graph() are not returned at all. But we won't worry
1370
# about that for now.
1371
for node_id, parent_ids in rg.iteritems():
1372
if parent_ids == ():
1373
rg[node_id] = (NULL_REVISION,)
1374
rg[NULL_REVISION] = ()
1379
raise ValueError('get_parent_map(None) is not valid')
1380
if NULL_REVISION in keys:
1381
keys.discard(NULL_REVISION)
1382
found_parents = {NULL_REVISION:()}
1384
return found_parents
1387
# TODO(Needs analysis): We could assume that the keys being requested
1388
# from get_parent_map are in a breadth first search, so typically they
1389
# will all be depth N from some common parent, and we don't have to
1390
# have the server iterate from the root parent, but rather from the
1391
# keys we're searching; and just tell the server the keyspace we
1392
# already have; but this may be more traffic again.
1394
# Transform self._parents_map into a search request recipe.
1395
# TODO: Manage this incrementally to avoid covering the same path
1396
# repeatedly. (The server will have to on each request, but the less
1397
# work done the better).
1399
# Negative caching notes:
1400
# new server sends missing when a request including the revid
1401
# 'include-missing:' is present in the request.
1402
# missing keys are serialised as missing:X, and we then call
1403
# provider.note_missing(X) for-all X
1404
parents_map = self._unstacked_provider.get_cached_map()
1405
if parents_map is None:
1406
# Repository is not locked, so there's no cache.
1408
# start_set is all the keys in the cache
1409
start_set = set(parents_map)
1410
# result set is all the references to keys in the cache
1411
result_parents = set()
1412
for parents in parents_map.itervalues():
1413
result_parents.update(parents)
1414
stop_keys = result_parents.difference(start_set)
1415
# We don't need to send ghosts back to the server as a position to
1417
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1418
key_count = len(parents_map)
1419
if (NULL_REVISION in result_parents
1420
and NULL_REVISION in self._unstacked_provider.missing_keys):
1421
# If we pruned NULL_REVISION from the stop_keys because it's also
1422
# in our cache of "missing" keys we need to increment our key count
1423
# by 1, because the reconsitituted SearchResult on the server will
1424
# still consider NULL_REVISION to be an included key.
1426
included_keys = start_set.intersection(result_parents)
1427
start_set.difference_update(included_keys)
1428
recipe = ('manual', start_set, stop_keys, key_count)
1429
body = self._serialise_search_recipe(recipe)
1430
path = self.bzrdir._path_for_remote_call(self._client)
1432
if type(key) is not str:
1434
"key %r not a plain string" % (key,))
1435
verb = 'Repository.get_parent_map'
1436
args = (path, 'include-missing:') + tuple(keys)
1438
response = self._call_with_body_bytes_expecting_body(
1440
except errors.UnknownSmartMethod:
1441
# Server does not support this method, so get the whole graph.
1442
# Worse, we have to force a disconnection, because the server now
1443
# doesn't realise it has a body on the wire to consume, so the
1444
# only way to recover is to abandon the connection.
1446
'Server is too old for fast get_parent_map, reconnecting. '
1447
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1449
# To avoid having to disconnect repeatedly, we keep track of the
1450
# fact the server doesn't understand remote methods added in 1.2.
1451
medium._remember_remote_is_before((1, 2))
1452
# Recurse just once and we should use the fallback code.
1453
return self._get_parent_map_rpc(keys)
1454
response_tuple, response_handler = response
1455
if response_tuple[0] not in ['ok']:
1456
response_handler.cancel_read_body()
1457
raise errors.UnexpectedSmartServerResponse(response_tuple)
1458
if response_tuple[0] == 'ok':
1459
coded = bz2.decompress(response_handler.read_body_bytes())
1461
# no revisions found
1463
lines = coded.split('\n')
1466
d = tuple(line.split())
1468
revision_graph[d[0]] = d[1:]
1471
if d[0].startswith('missing:'):
1473
self._unstacked_provider.note_missing_key(revid)
1475
# no parents - so give the Graph result
1477
revision_graph[d[0]] = (NULL_REVISION,)
1478
return revision_graph
1481
def get_signature_text(self, revision_id):
1483
return self._real_repository.get_signature_text(revision_id)
1486
def get_inventory_xml(self, revision_id):
1488
return self._real_repository.get_inventory_xml(revision_id)
1490
def deserialise_inventory(self, revision_id, xml):
1492
return self._real_repository.deserialise_inventory(revision_id, xml)
1494
def reconcile(self, other=None, thorough=False):
1496
return self._real_repository.reconcile(other=other, thorough=thorough)
1498
def all_revision_ids(self):
1500
return self._real_repository.all_revision_ids()
1503
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1505
return self._real_repository.get_deltas_for_revisions(revisions,
1506
specific_fileids=specific_fileids)
1509
def get_revision_delta(self, revision_id, specific_fileids=None):
1511
return self._real_repository.get_revision_delta(revision_id,
1512
specific_fileids=specific_fileids)
1515
def revision_trees(self, revision_ids):
1517
return self._real_repository.revision_trees(revision_ids)
1520
def get_revision_reconcile(self, revision_id):
1522
return self._real_repository.get_revision_reconcile(revision_id)
1525
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1527
return self._real_repository.check(revision_ids=revision_ids,
1528
callback_refs=callback_refs, check_repo=check_repo)
1530
def copy_content_into(self, destination, revision_id=None):
1532
return self._real_repository.copy_content_into(
1533
destination, revision_id=revision_id)
1535
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1536
# get a tarball of the remote repository, and copy from that into the
1538
from bzrlib import osutils
1540
# TODO: Maybe a progress bar while streaming the tarball?
1541
note("Copying repository content as tarball...")
1542
tar_file = self._get_tarball('bz2')
1543
if tar_file is None:
1545
destination = to_bzrdir.create_repository()
1547
tar = tarfile.open('repository', fileobj=tar_file,
1549
tmpdir = osutils.mkdtemp()
1551
_extract_tar(tar, tmpdir)
1552
tmp_bzrdir = BzrDir.open(tmpdir)
1553
tmp_repo = tmp_bzrdir.open_repository()
1554
tmp_repo.copy_content_into(destination, revision_id)
1556
osutils.rmtree(tmpdir)
1560
# TODO: Suggestion from john: using external tar is much faster than
1561
# python's tarfile library, but it may not work on windows.
1564
def inventories(self):
1565
"""Decorate the real repository for now.
1567
In the long term a full blown network facility is needed to
1568
avoid creating a real repository object locally.
1571
return self._real_repository.inventories
1574
def pack(self, hint=None):
1575
"""Compress the data within the repository.
1577
This is not currently implemented within the smart server.
1580
return self._real_repository.pack(hint=hint)
1583
def revisions(self):
1584
"""Decorate the real repository for now.
1586
In the short term this should become a real object to intercept graph
1589
In the long term a full blown network facility is needed.
1592
return self._real_repository.revisions
1594
def set_make_working_trees(self, new_value):
1596
new_value_str = "True"
1598
new_value_str = "False"
1599
path = self.bzrdir._path_for_remote_call(self._client)
1601
response = self._call(
1602
'Repository.set_make_working_trees', path, new_value_str)
1603
except errors.UnknownSmartMethod:
1605
self._real_repository.set_make_working_trees(new_value)
1607
if response[0] != 'ok':
1608
raise errors.UnexpectedSmartServerResponse(response)
1611
def signatures(self):
1612
"""Decorate the real repository for now.
1614
In the long term a full blown network facility is needed to avoid
1615
creating a real repository object locally.
1618
return self._real_repository.signatures
1621
def sign_revision(self, revision_id, gpg_strategy):
1623
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1627
"""Decorate the real repository for now.
1629
In the long term a full blown network facility is needed to avoid
1630
creating a real repository object locally.
1633
return self._real_repository.texts
1636
def get_revisions(self, revision_ids):
1638
return self._real_repository.get_revisions(revision_ids)
1640
def supports_rich_root(self):
1641
return self._format.rich_root_data
1643
def iter_reverse_revision_history(self, revision_id):
1645
return self._real_repository.iter_reverse_revision_history(revision_id)
1648
def _serializer(self):
1649
return self._format._serializer
1651
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1653
return self._real_repository.store_revision_signature(
1654
gpg_strategy, plaintext, revision_id)
1656
def add_signature_text(self, revision_id, signature):
1658
return self._real_repository.add_signature_text(revision_id, signature)
1660
def has_signature_for_revision_id(self, revision_id):
1662
return self._real_repository.has_signature_for_revision_id(revision_id)
1664
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1666
return self._real_repository.item_keys_introduced_by(revision_ids,
1667
_files_pb=_files_pb)
1669
def revision_graph_can_have_wrong_parents(self):
1670
# The answer depends on the remote repo format.
1672
return self._real_repository.revision_graph_can_have_wrong_parents()
1674
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1676
return self._real_repository._find_inconsistent_revision_parents(
1679
def _check_for_inconsistent_revision_parents(self):
1681
return self._real_repository._check_for_inconsistent_revision_parents()
1683
def _make_parents_provider(self, other=None):
1684
providers = [self._unstacked_provider]
1685
if other is not None:
1686
providers.insert(0, other)
1687
providers.extend(r._make_parents_provider() for r in
1688
self._fallback_repositories)
1689
return graph.StackedParentsProvider(providers)
1691
def _serialise_search_recipe(self, recipe):
1692
"""Serialise a graph search recipe.
1694
:param recipe: A search recipe (start, stop, count).
1695
:return: Serialised bytes.
1697
start_keys = ' '.join(recipe[1])
1698
stop_keys = ' '.join(recipe[2])
1699
count = str(recipe[3])
1700
return '\n'.join((start_keys, stop_keys, count))
1702
def _serialise_search_result(self, search_result):
1703
if isinstance(search_result, graph.PendingAncestryResult):
1704
parts = ['ancestry-of']
1705
parts.extend(search_result.heads)
1707
recipe = search_result.get_recipe()
1708
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1709
return '\n'.join(parts)
1712
path = self.bzrdir._path_for_remote_call(self._client)
1714
response = self._call('PackRepository.autopack', path)
1715
except errors.UnknownSmartMethod:
1717
self._real_repository._pack_collection.autopack()
1720
if response[0] != 'ok':
1721
raise errors.UnexpectedSmartServerResponse(response)
1724
class RemoteStreamSink(repository.StreamSink):
1726
def _insert_real(self, stream, src_format, resume_tokens):
1727
self.target_repo._ensure_real()
1728
sink = self.target_repo._real_repository._get_sink()
1729
result = sink.insert_stream(stream, src_format, resume_tokens)
1731
self.target_repo.autopack()
1734
def insert_stream(self, stream, src_format, resume_tokens):
1735
target = self.target_repo
1736
target._unstacked_provider.missing_keys.clear()
1737
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1738
if target._lock_token:
1739
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1740
lock_args = (target._lock_token or '',)
1742
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1744
client = target._client
1745
medium = client._medium
1746
path = target.bzrdir._path_for_remote_call(client)
1747
# Probe for the verb to use with an empty stream before sending the
1748
# real stream to it. We do this both to avoid the risk of sending a
1749
# large request that is then rejected, and because we don't want to
1750
# implement a way to buffer, rewind, or restart the stream.
1752
for verb, required_version in candidate_calls:
1753
if medium._is_remote_before(required_version):
1756
# We've already done the probing (and set _is_remote_before) on
1757
# a previous insert.
1760
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1762
response = client.call_with_body_stream(
1763
(verb, path, '') + lock_args, byte_stream)
1764
except errors.UnknownSmartMethod:
1765
medium._remember_remote_is_before(required_version)
1771
return self._insert_real(stream, src_format, resume_tokens)
1772
self._last_inv_record = None
1773
self._last_substream = None
1774
if required_version < (1, 19):
1775
# Remote side doesn't support inventory deltas. Wrap the stream to
1776
# make sure we don't send any. If the stream contains inventory
1777
# deltas we'll interrupt the smart insert_stream request and
1779
stream = self._stop_stream_if_inventory_delta(stream)
1780
byte_stream = smart_repo._stream_to_byte_stream(
1782
resume_tokens = ' '.join(resume_tokens)
1783
response = client.call_with_body_stream(
1784
(verb, path, resume_tokens) + lock_args, byte_stream)
1785
if response[0][0] not in ('ok', 'missing-basis'):
1786
raise errors.UnexpectedSmartServerResponse(response)
1787
if self._last_substream is not None:
1788
# The stream included an inventory-delta record, but the remote
1789
# side isn't new enough to support them. So we need to send the
1790
# rest of the stream via VFS.
1791
self.target_repo.refresh_data()
1792
return self._resume_stream_with_vfs(response, src_format)
1793
if response[0][0] == 'missing-basis':
1794
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1795
resume_tokens = tokens
1796
return resume_tokens, set(missing_keys)
1798
self.target_repo.refresh_data()
1801
def _resume_stream_with_vfs(self, response, src_format):
1802
"""Resume sending a stream via VFS, first resending the record and
1803
substream that couldn't be sent via an insert_stream verb.
1805
if response[0][0] == 'missing-basis':
1806
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1807
# Ignore missing_keys, we haven't finished inserting yet
1810
def resume_substream():
1811
# Yield the substream that was interrupted.
1812
for record in self._last_substream:
1814
self._last_substream = None
1815
def resume_stream():
1816
# Finish sending the interrupted substream
1817
yield ('inventory-deltas', resume_substream())
1818
# Then simply continue sending the rest of the stream.
1819
for substream_kind, substream in self._last_stream:
1820
yield substream_kind, substream
1821
return self._insert_real(resume_stream(), src_format, tokens)
1823
def _stop_stream_if_inventory_delta(self, stream):
1824
"""Normally this just lets the original stream pass-through unchanged.
1826
However if any 'inventory-deltas' substream occurs it will stop
1827
streaming, and store the interrupted substream and stream in
1828
self._last_substream and self._last_stream so that the stream can be
1829
resumed by _resume_stream_with_vfs.
1832
stream_iter = iter(stream)
1833
for substream_kind, substream in stream_iter:
1834
if substream_kind == 'inventory-deltas':
1835
self._last_substream = substream
1836
self._last_stream = stream_iter
1839
yield substream_kind, substream
1842
class RemoteStreamSource(repository.StreamSource):
1843
"""Stream data from a remote server."""
1845
def get_stream(self, search):
1846
if (self.from_repository._fallback_repositories and
1847
self.to_format._fetch_order == 'topological'):
1848
return self._real_stream(self.from_repository, search)
1851
repos = [self.from_repository]
1857
repos.extend(repo._fallback_repositories)
1858
sources.append(repo)
1859
return self.missing_parents_chain(search, sources)
1861
def get_stream_for_missing_keys(self, missing_keys):
1862
self.from_repository._ensure_real()
1863
real_repo = self.from_repository._real_repository
1864
real_source = real_repo._get_source(self.to_format)
1865
return real_source.get_stream_for_missing_keys(missing_keys)
1867
def _real_stream(self, repo, search):
1868
"""Get a stream for search from repo.
1870
This never called RemoteStreamSource.get_stream, and is a heler
1871
for RemoteStreamSource._get_stream to allow getting a stream
1872
reliably whether fallback back because of old servers or trying
1873
to stream from a non-RemoteRepository (which the stacked support
1876
source = repo._get_source(self.to_format)
1877
if isinstance(source, RemoteStreamSource):
1879
source = repo._real_repository._get_source(self.to_format)
1880
return source.get_stream(search)
1882
def _get_stream(self, repo, search):
1883
"""Core worker to get a stream from repo for search.
1885
This is used by both get_stream and the stacking support logic. It
1886
deliberately gets a stream for repo which does not need to be
1887
self.from_repository. In the event that repo is not Remote, or
1888
cannot do a smart stream, a fallback is made to the generic
1889
repository._get_stream() interface, via self._real_stream.
1891
In the event of stacking, streams from _get_stream will not
1892
contain all the data for search - this is normal (see get_stream).
1894
:param repo: A repository.
1895
:param search: A search.
1897
# Fallbacks may be non-smart
1898
if not isinstance(repo, RemoteRepository):
1899
return self._real_stream(repo, search)
1900
client = repo._client
1901
medium = client._medium
1902
path = repo.bzrdir._path_for_remote_call(client)
1903
search_bytes = repo._serialise_search_result(search)
1904
args = (path, self.to_format.network_name())
1906
('Repository.get_stream_1.19', (1, 19)),
1907
('Repository.get_stream', (1, 13))]
1909
for verb, version in candidate_verbs:
1910
if medium._is_remote_before(version):
1913
response = repo._call_with_body_bytes_expecting_body(
1914
verb, args, search_bytes)
1915
except errors.UnknownSmartMethod:
1916
medium._remember_remote_is_before(version)
1918
response_tuple, response_handler = response
1922
return self._real_stream(repo, search)
1923
if response_tuple[0] != 'ok':
1924
raise errors.UnexpectedSmartServerResponse(response_tuple)
1925
byte_stream = response_handler.read_streamed_body()
1926
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1927
if src_format.network_name() != repo._format.network_name():
1928
raise AssertionError(
1929
"Mismatched RemoteRepository and stream src %r, %r" % (
1930
src_format.network_name(), repo._format.network_name()))
1933
def missing_parents_chain(self, search, sources):
1934
"""Chain multiple streams together to handle stacking.
1936
:param search: The overall search to satisfy with streams.
1937
:param sources: A list of Repository objects to query.
1939
self.from_serialiser = self.from_repository._format._serializer
1940
self.seen_revs = set()
1941
self.referenced_revs = set()
1942
# If there are heads in the search, or the key count is > 0, we are not
1944
while not search.is_empty() and len(sources) > 1:
1945
source = sources.pop(0)
1946
stream = self._get_stream(source, search)
1947
for kind, substream in stream:
1948
if kind != 'revisions':
1949
yield kind, substream
1951
yield kind, self.missing_parents_rev_handler(substream)
1952
search = search.refine(self.seen_revs, self.referenced_revs)
1953
self.seen_revs = set()
1954
self.referenced_revs = set()
1955
if not search.is_empty():
1956
for kind, stream in self._get_stream(sources[0], search):
1959
def missing_parents_rev_handler(self, substream):
1960
for content in substream:
1961
revision_bytes = content.get_bytes_as('fulltext')
1962
revision = self.from_serialiser.read_revision_from_string(
1964
self.seen_revs.add(content.key[-1])
1965
self.referenced_revs.update(revision.parent_ids)
1969
class RemoteBranchLockableFiles(LockableFiles):
1970
"""A 'LockableFiles' implementation that talks to a smart server.
1972
This is not a public interface class.
1975
def __init__(self, bzrdir, _client):
1976
self.bzrdir = bzrdir
1977
self._client = _client
1978
self._need_find_modes = True
1979
LockableFiles.__init__(
1980
self, bzrdir.get_branch_transport(None),
1981
'lock', lockdir.LockDir)
1983
def _find_modes(self):
1984
# RemoteBranches don't let the client set the mode of control files.
1985
self._dir_mode = None
1986
self._file_mode = None
1989
class RemoteBranchFormat(branch.BranchFormat):
1991
def __init__(self, network_name=None):
1992
super(RemoteBranchFormat, self).__init__()
1993
self._matchingbzrdir = RemoteBzrDirFormat()
1994
self._matchingbzrdir.set_branch_format(self)
1995
self._custom_format = None
1996
self._network_name = network_name
1998
def __eq__(self, other):
1999
return (isinstance(other, RemoteBranchFormat) and
2000
self.__dict__ == other.__dict__)
2002
def _ensure_real(self):
2003
if self._custom_format is None:
2004
self._custom_format = branch.network_format_registry.get(
2007
def get_format_description(self):
2009
return 'Remote: ' + self._custom_format.get_format_description()
2011
def network_name(self):
2012
return self._network_name
2014
def open(self, a_bzrdir, ignore_fallbacks=False):
2015
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2017
def _vfs_initialize(self, a_bzrdir):
2018
# Initialisation when using a local bzrdir object, or a non-vfs init
2019
# method is not available on the server.
2020
# self._custom_format is always set - the start of initialize ensures
2022
if isinstance(a_bzrdir, RemoteBzrDir):
2023
a_bzrdir._ensure_real()
2024
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2026
# We assume the bzrdir is parameterised; it may not be.
2027
result = self._custom_format.initialize(a_bzrdir)
2028
if (isinstance(a_bzrdir, RemoteBzrDir) and
2029
not isinstance(result, RemoteBranch)):
2030
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2033
def initialize(self, a_bzrdir):
2034
# 1) get the network name to use.
2035
if self._custom_format:
2036
network_name = self._custom_format.network_name()
2038
# Select the current bzrlib default and ask for that.
2039
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2040
reference_format = reference_bzrdir_format.get_branch_format()
2041
self._custom_format = reference_format
2042
network_name = reference_format.network_name()
2043
# Being asked to create on a non RemoteBzrDir:
2044
if not isinstance(a_bzrdir, RemoteBzrDir):
2045
return self._vfs_initialize(a_bzrdir)
2046
medium = a_bzrdir._client._medium
2047
if medium._is_remote_before((1, 13)):
2048
return self._vfs_initialize(a_bzrdir)
2049
# Creating on a remote bzr dir.
2050
# 2) try direct creation via RPC
2051
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2052
verb = 'BzrDir.create_branch'
2054
response = a_bzrdir._call(verb, path, network_name)
2055
except errors.UnknownSmartMethod:
2056
# Fallback - use vfs methods
2057
medium._remember_remote_is_before((1, 13))
2058
return self._vfs_initialize(a_bzrdir)
2059
if response[0] != 'ok':
2060
raise errors.UnexpectedSmartServerResponse(response)
2061
# Turn the response into a RemoteRepository object.
2062
format = RemoteBranchFormat(network_name=response[1])
2063
repo_format = response_tuple_to_repo_format(response[3:])
2064
if response[2] == '':
2065
repo_bzrdir = a_bzrdir
2067
repo_bzrdir = RemoteBzrDir(
2068
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2070
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2071
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2072
format=format, setup_stacking=False)
2073
# XXX: We know this is a new branch, so it must have revno 0, revid
2074
# NULL_REVISION. Creating the branch locked would make this be unable
2075
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2076
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2077
return remote_branch
2079
def make_tags(self, branch):
2081
return self._custom_format.make_tags(branch)
2083
def supports_tags(self):
2084
# Remote branches might support tags, but we won't know until we
2085
# access the real remote branch.
2087
return self._custom_format.supports_tags()
2089
def supports_stacking(self):
2091
return self._custom_format.supports_stacking()
2093
def supports_set_append_revisions_only(self):
2095
return self._custom_format.supports_set_append_revisions_only()
2098
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2099
"""Branch stored on a server accessed by HPSS RPC.
2101
At the moment most operations are mapped down to simple file operations.
2104
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2105
_client=None, format=None, setup_stacking=True):
2106
"""Create a RemoteBranch instance.
2108
:param real_branch: An optional local implementation of the branch
2109
format, usually accessing the data via the VFS.
2110
:param _client: Private parameter for testing.
2111
:param format: A RemoteBranchFormat object, None to create one
2112
automatically. If supplied it should have a network_name already
2114
:param setup_stacking: If True make an RPC call to determine the
2115
stacked (or not) status of the branch. If False assume the branch
2118
# We intentionally don't call the parent class's __init__, because it
2119
# will try to assign to self.tags, which is a property in this subclass.
2120
# And the parent's __init__ doesn't do much anyway.
2121
self.bzrdir = remote_bzrdir
2122
if _client is not None:
2123
self._client = _client
2125
self._client = remote_bzrdir._client
2126
self.repository = remote_repository
2127
if real_branch is not None:
2128
self._real_branch = real_branch
2129
# Give the remote repository the matching real repo.
2130
real_repo = self._real_branch.repository
2131
if isinstance(real_repo, RemoteRepository):
2132
real_repo._ensure_real()
2133
real_repo = real_repo._real_repository
2134
self.repository._set_real_repository(real_repo)
2135
# Give the branch the remote repository to let fast-pathing happen.
2136
self._real_branch.repository = self.repository
2138
self._real_branch = None
2139
# Fill out expected attributes of branch for bzrlib API users.
2140
self._clear_cached_state()
2141
self.base = self.bzrdir.root_transport.base
2142
self._control_files = None
2143
self._lock_mode = None
2144
self._lock_token = None
2145
self._repo_lock_token = None
2146
self._lock_count = 0
2147
self._leave_lock = False
2148
# Setup a format: note that we cannot call _ensure_real until all the
2149
# attributes above are set: This code cannot be moved higher up in this
2152
self._format = RemoteBranchFormat()
2153
if real_branch is not None:
2154
self._format._network_name = \
2155
self._real_branch._format.network_name()
2157
self._format = format
2158
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2159
# branch.open_branch method.
2160
self._real_ignore_fallbacks = not setup_stacking
2161
if not self._format._network_name:
2162
# Did not get from open_branchV2 - old server.
2164
self._format._network_name = \
2165
self._real_branch._format.network_name()
2166
self.tags = self._format.make_tags(self)
2167
# The base class init is not called, so we duplicate this:
2168
hooks = branch.Branch.hooks['open']
2171
self._is_stacked = False
2173
self._setup_stacking()
2175
def _setup_stacking(self):
2176
# configure stacking into the remote repository, by reading it from
2179
fallback_url = self.get_stacked_on_url()
2180
except (errors.NotStacked, errors.UnstackableBranchFormat,
2181
errors.UnstackableRepositoryFormat), e:
2183
self._is_stacked = True
2184
self._activate_fallback_location(fallback_url)
2186
def _get_config(self):
2187
return RemoteBranchConfig(self)
2189
def _get_real_transport(self):
2190
# if we try vfs access, return the real branch's vfs transport
2192
return self._real_branch._transport
2194
_transport = property(_get_real_transport)
2197
return "%s(%s)" % (self.__class__.__name__, self.base)
2201
def _ensure_real(self):
2202
"""Ensure that there is a _real_branch set.
2204
Used before calls to self._real_branch.
2206
if self._real_branch is None:
2207
if not vfs.vfs_enabled():
2208
raise AssertionError('smart server vfs must be enabled '
2209
'to use vfs implementation')
2210
self.bzrdir._ensure_real()
2211
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2212
ignore_fallbacks=self._real_ignore_fallbacks)
2213
if self.repository._real_repository is None:
2214
# Give the remote repository the matching real repo.
2215
real_repo = self._real_branch.repository
2216
if isinstance(real_repo, RemoteRepository):
2217
real_repo._ensure_real()
2218
real_repo = real_repo._real_repository
2219
self.repository._set_real_repository(real_repo)
2220
# Give the real branch the remote repository to let fast-pathing
2222
self._real_branch.repository = self.repository
2223
if self._lock_mode == 'r':
2224
self._real_branch.lock_read()
2225
elif self._lock_mode == 'w':
2226
self._real_branch.lock_write(token=self._lock_token)
2228
def _translate_error(self, err, **context):
2229
self.repository._translate_error(err, branch=self, **context)
2231
def _clear_cached_state(self):
2232
super(RemoteBranch, self)._clear_cached_state()
2233
if self._real_branch is not None:
2234
self._real_branch._clear_cached_state()
2236
def _clear_cached_state_of_remote_branch_only(self):
2237
"""Like _clear_cached_state, but doesn't clear the cache of
2240
This is useful when falling back to calling a method of
2241
self._real_branch that changes state. In that case the underlying
2242
branch changes, so we need to invalidate this RemoteBranch's cache of
2243
it. However, there's no need to invalidate the _real_branch's cache
2244
too, in fact doing so might harm performance.
2246
super(RemoteBranch, self)._clear_cached_state()
2249
def control_files(self):
2250
# Defer actually creating RemoteBranchLockableFiles until its needed,
2251
# because it triggers an _ensure_real that we otherwise might not need.
2252
if self._control_files is None:
2253
self._control_files = RemoteBranchLockableFiles(
2254
self.bzrdir, self._client)
2255
return self._control_files
2257
def _get_checkout_format(self):
2259
return self._real_branch._get_checkout_format()
2261
def get_physical_lock_status(self):
2262
"""See Branch.get_physical_lock_status()."""
2263
# should be an API call to the server, as branches must be lockable.
2265
return self._real_branch.get_physical_lock_status()
2267
def get_stacked_on_url(self):
2268
"""Get the URL this branch is stacked against.
2270
:raises NotStacked: If the branch is not stacked.
2271
:raises UnstackableBranchFormat: If the branch does not support
2273
:raises UnstackableRepositoryFormat: If the repository does not support
2277
# there may not be a repository yet, so we can't use
2278
# self._translate_error, so we can't use self._call either.
2279
response = self._client.call('Branch.get_stacked_on_url',
2280
self._remote_path())
2281
except errors.ErrorFromSmartServer, err:
2282
# there may not be a repository yet, so we can't call through
2283
# its _translate_error
2284
_translate_error(err, branch=self)
2285
except errors.UnknownSmartMethod, err:
2287
return self._real_branch.get_stacked_on_url()
2288
if response[0] != 'ok':
2289
raise errors.UnexpectedSmartServerResponse(response)
2292
def set_stacked_on_url(self, url):
2293
branch.Branch.set_stacked_on_url(self, url)
2295
self._is_stacked = False
2297
self._is_stacked = True
2299
def _vfs_get_tags_bytes(self):
2301
return self._real_branch._get_tags_bytes()
2303
def _get_tags_bytes(self):
2304
medium = self._client._medium
2305
if medium._is_remote_before((1, 13)):
2306
return self._vfs_get_tags_bytes()
2308
response = self._call('Branch.get_tags_bytes', self._remote_path())
2309
except errors.UnknownSmartMethod:
2310
medium._remember_remote_is_before((1, 13))
2311
return self._vfs_get_tags_bytes()
2314
def _vfs_set_tags_bytes(self, bytes):
2316
return self._real_branch._set_tags_bytes(bytes)
2318
def _set_tags_bytes(self, bytes):
2319
medium = self._client._medium
2320
if medium._is_remote_before((1, 18)):
2321
self._vfs_set_tags_bytes(bytes)
2325
self._remote_path(), self._lock_token, self._repo_lock_token)
2326
response = self._call_with_body_bytes(
2327
'Branch.set_tags_bytes', args, bytes)
2328
except errors.UnknownSmartMethod:
2329
medium._remember_remote_is_before((1, 18))
2330
self._vfs_set_tags_bytes(bytes)
2332
def lock_read(self):
2333
self.repository.lock_read()
2334
if not self._lock_mode:
2335
self._note_lock('r')
2336
self._lock_mode = 'r'
2337
self._lock_count = 1
2338
if self._real_branch is not None:
2339
self._real_branch.lock_read()
2341
self._lock_count += 1
2343
def _remote_lock_write(self, token):
2345
branch_token = repo_token = ''
2347
branch_token = token
2348
repo_token = self.repository.lock_write()
2349
self.repository.unlock()
2350
err_context = {'token': token}
2351
response = self._call(
2352
'Branch.lock_write', self._remote_path(), branch_token,
2353
repo_token or '', **err_context)
2354
if response[0] != 'ok':
2355
raise errors.UnexpectedSmartServerResponse(response)
2356
ok, branch_token, repo_token = response
2357
return branch_token, repo_token
2359
def lock_write(self, token=None):
2360
if not self._lock_mode:
2361
self._note_lock('w')
2362
# Lock the branch and repo in one remote call.
2363
remote_tokens = self._remote_lock_write(token)
2364
self._lock_token, self._repo_lock_token = remote_tokens
2365
if not self._lock_token:
2366
raise SmartProtocolError('Remote server did not return a token!')
2367
# Tell the self.repository object that it is locked.
2368
self.repository.lock_write(
2369
self._repo_lock_token, _skip_rpc=True)
2371
if self._real_branch is not None:
2372
self._real_branch.lock_write(token=self._lock_token)
2373
if token is not None:
2374
self._leave_lock = True
2376
self._leave_lock = False
2377
self._lock_mode = 'w'
2378
self._lock_count = 1
2379
elif self._lock_mode == 'r':
2380
raise errors.ReadOnlyTransaction
2382
if token is not None:
2383
# A token was given to lock_write, and we're relocking, so
2384
# check that the given token actually matches the one we
2386
if token != self._lock_token:
2387
raise errors.TokenMismatch(token, self._lock_token)
2388
self._lock_count += 1
2389
# Re-lock the repository too.
2390
self.repository.lock_write(self._repo_lock_token)
2391
return self._lock_token or None
2393
def _unlock(self, branch_token, repo_token):
2394
err_context = {'token': str((branch_token, repo_token))}
2395
response = self._call(
2396
'Branch.unlock', self._remote_path(), branch_token,
2397
repo_token or '', **err_context)
2398
if response == ('ok',):
2400
raise errors.UnexpectedSmartServerResponse(response)
2402
@only_raises(errors.LockNotHeld, errors.LockBroken)
2405
self._lock_count -= 1
2406
if not self._lock_count:
2407
self._clear_cached_state()
2408
mode = self._lock_mode
2409
self._lock_mode = None
2410
if self._real_branch is not None:
2411
if (not self._leave_lock and mode == 'w' and
2412
self._repo_lock_token):
2413
# If this RemoteBranch will remove the physical lock
2414
# for the repository, make sure the _real_branch
2415
# doesn't do it first. (Because the _real_branch's
2416
# repository is set to be the RemoteRepository.)
2417
self._real_branch.repository.leave_lock_in_place()
2418
self._real_branch.unlock()
2420
# Only write-locked branched need to make a remote method
2421
# call to perform the unlock.
2423
if not self._lock_token:
2424
raise AssertionError('Locked, but no token!')
2425
branch_token = self._lock_token
2426
repo_token = self._repo_lock_token
2427
self._lock_token = None
2428
self._repo_lock_token = None
2429
if not self._leave_lock:
2430
self._unlock(branch_token, repo_token)
2432
self.repository.unlock()
2434
def break_lock(self):
2436
return self._real_branch.break_lock()
2438
def leave_lock_in_place(self):
2439
if not self._lock_token:
2440
raise NotImplementedError(self.leave_lock_in_place)
2441
self._leave_lock = True
2443
def dont_leave_lock_in_place(self):
2444
if not self._lock_token:
2445
raise NotImplementedError(self.dont_leave_lock_in_place)
2446
self._leave_lock = False
2449
def get_rev_id(self, revno, history=None):
2451
return _mod_revision.NULL_REVISION
2452
last_revision_info = self.last_revision_info()
2453
ok, result = self.repository.get_rev_id_for_revno(
2454
revno, last_revision_info)
2457
missing_parent = result[1]
2458
# Either the revision named by the server is missing, or its parent
2459
# is. Call get_parent_map to determine which, so that we report a
2461
parent_map = self.repository.get_parent_map([missing_parent])
2462
if missing_parent in parent_map:
2463
missing_parent = parent_map[missing_parent]
2464
raise errors.RevisionNotPresent(missing_parent, self.repository)
2466
def _last_revision_info(self):
2467
response = self._call('Branch.last_revision_info', self._remote_path())
2468
if response[0] != 'ok':
2469
raise SmartProtocolError('unexpected response code %s' % (response,))
2470
revno = int(response[1])
2471
last_revision = response[2]
2472
return (revno, last_revision)
2474
def _gen_revision_history(self):
2475
"""See Branch._gen_revision_history()."""
2476
if self._is_stacked:
2478
return self._real_branch._gen_revision_history()
2479
response_tuple, response_handler = self._call_expecting_body(
2480
'Branch.revision_history', self._remote_path())
2481
if response_tuple[0] != 'ok':
2482
raise errors.UnexpectedSmartServerResponse(response_tuple)
2483
result = response_handler.read_body_bytes().split('\x00')
2488
def _remote_path(self):
2489
return self.bzrdir._path_for_remote_call(self._client)
2491
def _set_last_revision_descendant(self, revision_id, other_branch,
2492
allow_diverged=False, allow_overwrite_descendant=False):
2493
# This performs additional work to meet the hook contract; while its
2494
# undesirable, we have to synthesise the revno to call the hook, and
2495
# not calling the hook is worse as it means changes can't be prevented.
2496
# Having calculated this though, we can't just call into
2497
# set_last_revision_info as a simple call, because there is a set_rh
2498
# hook that some folk may still be using.
2499
old_revno, old_revid = self.last_revision_info()
2500
history = self._lefthand_history(revision_id)
2501
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2502
err_context = {'other_branch': other_branch}
2503
response = self._call('Branch.set_last_revision_ex',
2504
self._remote_path(), self._lock_token, self._repo_lock_token,
2505
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2507
self._clear_cached_state()
2508
if len(response) != 3 and response[0] != 'ok':
2509
raise errors.UnexpectedSmartServerResponse(response)
2510
new_revno, new_revision_id = response[1:]
2511
self._last_revision_info_cache = new_revno, new_revision_id
2512
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2513
if self._real_branch is not None:
2514
cache = new_revno, new_revision_id
2515
self._real_branch._last_revision_info_cache = cache
2517
def _set_last_revision(self, revision_id):
2518
old_revno, old_revid = self.last_revision_info()
2519
# This performs additional work to meet the hook contract; while its
2520
# undesirable, we have to synthesise the revno to call the hook, and
2521
# not calling the hook is worse as it means changes can't be prevented.
2522
# Having calculated this though, we can't just call into
2523
# set_last_revision_info as a simple call, because there is a set_rh
2524
# hook that some folk may still be using.
2525
history = self._lefthand_history(revision_id)
2526
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2527
self._clear_cached_state()
2528
response = self._call('Branch.set_last_revision',
2529
self._remote_path(), self._lock_token, self._repo_lock_token,
2531
if response != ('ok',):
2532
raise errors.UnexpectedSmartServerResponse(response)
2533
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2536
def set_revision_history(self, rev_history):
2537
# Send just the tip revision of the history; the server will generate
2538
# the full history from that. If the revision doesn't exist in this
2539
# branch, NoSuchRevision will be raised.
2540
if rev_history == []:
2543
rev_id = rev_history[-1]
2544
self._set_last_revision(rev_id)
2545
for hook in branch.Branch.hooks['set_rh']:
2546
hook(self, rev_history)
2547
self._cache_revision_history(rev_history)
2549
def _get_parent_location(self):
2550
medium = self._client._medium
2551
if medium._is_remote_before((1, 13)):
2552
return self._vfs_get_parent_location()
2554
response = self._call('Branch.get_parent', self._remote_path())
2555
except errors.UnknownSmartMethod:
2556
medium._remember_remote_is_before((1, 13))
2557
return self._vfs_get_parent_location()
2558
if len(response) != 1:
2559
raise errors.UnexpectedSmartServerResponse(response)
2560
parent_location = response[0]
2561
if parent_location == '':
2563
return parent_location
2565
def _vfs_get_parent_location(self):
2567
return self._real_branch._get_parent_location()
2569
def _set_parent_location(self, url):
2570
medium = self._client._medium
2571
if medium._is_remote_before((1, 15)):
2572
return self._vfs_set_parent_location(url)
2574
call_url = url or ''
2575
if type(call_url) is not str:
2576
raise AssertionError('url must be a str or None (%s)' % url)
2577
response = self._call('Branch.set_parent_location',
2578
self._remote_path(), self._lock_token, self._repo_lock_token,
2580
except errors.UnknownSmartMethod:
2581
medium._remember_remote_is_before((1, 15))
2582
return self._vfs_set_parent_location(url)
2584
raise errors.UnexpectedSmartServerResponse(response)
2586
def _vfs_set_parent_location(self, url):
2588
return self._real_branch._set_parent_location(url)
2591
def pull(self, source, overwrite=False, stop_revision=None,
2593
self._clear_cached_state_of_remote_branch_only()
2595
return self._real_branch.pull(
2596
source, overwrite=overwrite, stop_revision=stop_revision,
2597
_override_hook_target=self, **kwargs)
2600
def push(self, target, overwrite=False, stop_revision=None):
2602
return self._real_branch.push(
2603
target, overwrite=overwrite, stop_revision=stop_revision,
2604
_override_hook_source_branch=self)
2606
def is_locked(self):
2607
return self._lock_count >= 1
2610
def revision_id_to_revno(self, revision_id):
2612
return self._real_branch.revision_id_to_revno(revision_id)
2615
def set_last_revision_info(self, revno, revision_id):
2616
# XXX: These should be returned by the set_last_revision_info verb
2617
old_revno, old_revid = self.last_revision_info()
2618
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2619
revision_id = ensure_null(revision_id)
2621
response = self._call('Branch.set_last_revision_info',
2622
self._remote_path(), self._lock_token, self._repo_lock_token,
2623
str(revno), revision_id)
2624
except errors.UnknownSmartMethod:
2626
self._clear_cached_state_of_remote_branch_only()
2627
self._real_branch.set_last_revision_info(revno, revision_id)
2628
self._last_revision_info_cache = revno, revision_id
2630
if response == ('ok',):
2631
self._clear_cached_state()
2632
self._last_revision_info_cache = revno, revision_id
2633
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2634
# Update the _real_branch's cache too.
2635
if self._real_branch is not None:
2636
cache = self._last_revision_info_cache
2637
self._real_branch._last_revision_info_cache = cache
2639
raise errors.UnexpectedSmartServerResponse(response)
2642
def generate_revision_history(self, revision_id, last_rev=None,
2644
medium = self._client._medium
2645
if not medium._is_remote_before((1, 6)):
2646
# Use a smart method for 1.6 and above servers
2648
self._set_last_revision_descendant(revision_id, other_branch,
2649
allow_diverged=True, allow_overwrite_descendant=True)
2651
except errors.UnknownSmartMethod:
2652
medium._remember_remote_is_before((1, 6))
2653
self._clear_cached_state_of_remote_branch_only()
2654
self.set_revision_history(self._lefthand_history(revision_id,
2655
last_rev=last_rev,other_branch=other_branch))
2657
def set_push_location(self, location):
2659
return self._real_branch.set_push_location(location)
2662
class RemoteConfig(object):
2663
"""A Config that reads and writes from smart verbs.
2665
It is a low-level object that considers config data to be name/value pairs
2666
that may be associated with a section. Assigning meaning to the these
2667
values is done at higher levels like bzrlib.config.TreeConfig.
2670
def get_option(self, name, section=None, default=None):
2671
"""Return the value associated with a named option.
2673
:param name: The name of the value
2674
:param section: The section the option is in (if any)
2675
:param default: The value to return if the value is not set
2676
:return: The value or default value
2679
configobj = self._get_configobj()
2681
section_obj = configobj
2684
section_obj = configobj[section]
2687
return section_obj.get(name, default)
2688
except errors.UnknownSmartMethod:
2689
return self._vfs_get_option(name, section, default)
2691
def _response_to_configobj(self, response):
2692
if len(response[0]) and response[0][0] != 'ok':
2693
raise errors.UnexpectedSmartServerResponse(response)
2694
lines = response[1].read_body_bytes().splitlines()
2695
return config.ConfigObj(lines, encoding='utf-8')
2698
class RemoteBranchConfig(RemoteConfig):
2699
"""A RemoteConfig for Branches."""
2701
def __init__(self, branch):
2702
self._branch = branch
2704
def _get_configobj(self):
2705
path = self._branch._remote_path()
2706
response = self._branch._client.call_expecting_body(
2707
'Branch.get_config_file', path)
2708
return self._response_to_configobj(response)
2710
def set_option(self, value, name, section=None):
2711
"""Set the value associated with a named option.
2713
:param value: The value to set
2714
:param name: The name of the value to set
2715
:param section: The section the option is in (if any)
2717
medium = self._branch._client._medium
2718
if medium._is_remote_before((1, 14)):
2719
return self._vfs_set_option(value, name, section)
2721
path = self._branch._remote_path()
2722
response = self._branch._client.call('Branch.set_config_option',
2723
path, self._branch._lock_token, self._branch._repo_lock_token,
2724
value.encode('utf8'), name, section or '')
2725
except errors.UnknownSmartMethod:
2726
medium._remember_remote_is_before((1, 14))
2727
return self._vfs_set_option(value, name, section)
2729
raise errors.UnexpectedSmartServerResponse(response)
2731
def _real_object(self):
2732
self._branch._ensure_real()
2733
return self._branch._real_branch
2735
def _vfs_set_option(self, value, name, section=None):
2736
return self._real_object()._get_config().set_option(
2737
value, name, section)
2740
class RemoteBzrDirConfig(RemoteConfig):
2741
"""A RemoteConfig for BzrDirs."""
2743
def __init__(self, bzrdir):
2744
self._bzrdir = bzrdir
2746
def _get_configobj(self):
2747
medium = self._bzrdir._client._medium
2748
verb = 'BzrDir.get_config_file'
2749
if medium._is_remote_before((1, 15)):
2750
raise errors.UnknownSmartMethod(verb)
2751
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2752
response = self._bzrdir._call_expecting_body(
2754
return self._response_to_configobj(response)
2756
def _vfs_get_option(self, name, section, default):
2757
return self._real_object()._get_config().get_option(
2758
name, section, default)
2760
def set_option(self, value, name, section=None):
2761
"""Set the value associated with a named option.
2763
:param value: The value to set
2764
:param name: The name of the value to set
2765
:param section: The section the option is in (if any)
2767
return self._real_object()._get_config().set_option(
2768
value, name, section)
2770
def _real_object(self):
2771
self._bzrdir._ensure_real()
2772
return self._bzrdir._real_bzrdir
2776
def _extract_tar(tar, to_dir):
2777
"""Extract all the contents of a tarfile object.
2779
A replacement for extractall, which is not present in python2.4
2782
tar.extract(tarinfo, to_dir)
2785
def _translate_error(err, **context):
2786
"""Translate an ErrorFromSmartServer into a more useful error.
2788
Possible context keys:
2796
If the error from the server doesn't match a known pattern, then
2797
UnknownErrorFromSmartServer is raised.
2801
return context[name]
2802
except KeyError, key_err:
2803
mutter('Missing key %r in context %r', key_err.args[0], context)
2806
"""Get the path from the context if present, otherwise use first error
2810
return context['path']
2811
except KeyError, key_err:
2813
return err.error_args[0]
2814
except IndexError, idx_err:
2816
'Missing key %r in context %r', key_err.args[0], context)
2819
if err.error_verb == 'IncompatibleRepositories':
2820
raise errors.IncompatibleRepositories(err.error_args[0],
2821
err.error_args[1], err.error_args[2])
2822
elif err.error_verb == 'NoSuchRevision':
2823
raise NoSuchRevision(find('branch'), err.error_args[0])
2824
elif err.error_verb == 'nosuchrevision':
2825
raise NoSuchRevision(find('repository'), err.error_args[0])
2826
elif err.error_tuple == ('nobranch',):
2827
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2828
elif err.error_verb == 'norepository':
2829
raise errors.NoRepositoryPresent(find('bzrdir'))
2830
elif err.error_verb == 'LockContention':
2831
raise errors.LockContention('(remote lock)')
2832
elif err.error_verb == 'UnlockableTransport':
2833
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2834
elif err.error_verb == 'LockFailed':
2835
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2836
elif err.error_verb == 'TokenMismatch':
2837
raise errors.TokenMismatch(find('token'), '(remote token)')
2838
elif err.error_verb == 'Diverged':
2839
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2840
elif err.error_verb == 'TipChangeRejected':
2841
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2842
elif err.error_verb == 'UnstackableBranchFormat':
2843
raise errors.UnstackableBranchFormat(*err.error_args)
2844
elif err.error_verb == 'UnstackableRepositoryFormat':
2845
raise errors.UnstackableRepositoryFormat(*err.error_args)
2846
elif err.error_verb == 'NotStacked':
2847
raise errors.NotStacked(branch=find('branch'))
2848
elif err.error_verb == 'PermissionDenied':
2850
if len(err.error_args) >= 2:
2851
extra = err.error_args[1]
2854
raise errors.PermissionDenied(path, extra=extra)
2855
elif err.error_verb == 'ReadError':
2857
raise errors.ReadError(path)
2858
elif err.error_verb == 'NoSuchFile':
2860
raise errors.NoSuchFile(path)
2861
elif err.error_verb == 'FileExists':
2862
raise errors.FileExists(err.error_args[0])
2863
elif err.error_verb == 'DirectoryNotEmpty':
2864
raise errors.DirectoryNotEmpty(err.error_args[0])
2865
elif err.error_verb == 'ShortReadvError':
2866
args = err.error_args
2867
raise errors.ShortReadvError(
2868
args[0], int(args[1]), int(args[2]), int(args[3]))
2869
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2870
encoding = str(err.error_args[0]) # encoding must always be a string
2871
val = err.error_args[1]
2872
start = int(err.error_args[2])
2873
end = int(err.error_args[3])
2874
reason = str(err.error_args[4]) # reason must always be a string
2875
if val.startswith('u:'):
2876
val = val[2:].decode('utf-8')
2877
elif val.startswith('s:'):
2878
val = val[2:].decode('base64')
2879
if err.error_verb == 'UnicodeDecodeError':
2880
raise UnicodeDecodeError(encoding, val, start, end, reason)
2881
elif err.error_verb == 'UnicodeEncodeError':
2882
raise UnicodeEncodeError(encoding, val, start, end, reason)
2883
elif err.error_verb == 'ReadOnlyError':
2884
raise errors.TransportNotPossible('readonly transport')
2885
raise errors.UnknownErrorFromSmartServer(err)