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):
604
return 'bzr remote repository'
606
def __eq__(self, other):
607
return self.__class__ is other.__class__
609
def network_name(self):
610
if self._network_name:
611
return self._network_name
612
self._creating_repo._ensure_real()
613
return self._creating_repo._real_repository._format.network_name()
616
def pack_compresses(self):
618
return self._custom_format.pack_compresses
621
def _serializer(self):
623
return self._custom_format._serializer
626
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
627
"""Repository accessed over rpc.
629
For the moment most operations are performed using local transport-backed
633
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
634
"""Create a RemoteRepository instance.
636
:param remote_bzrdir: The bzrdir hosting this repository.
637
:param format: The RemoteFormat object to use.
638
:param real_repository: If not None, a local implementation of the
639
repository logic for the repository, usually accessing the data
641
:param _client: Private testing parameter - override the smart client
642
to be used by the repository.
645
self._real_repository = real_repository
647
self._real_repository = None
648
self.bzrdir = remote_bzrdir
650
self._client = remote_bzrdir._client
652
self._client = _client
653
self._format = format
654
self._lock_mode = None
655
self._lock_token = None
657
self._leave_lock = False
658
# Cache of revision parents; misses are cached during read locks, and
659
# write locks when no _real_repository has been set.
660
self._unstacked_provider = graph.CachingParentsProvider(
661
get_parent_map=self._get_parent_map_rpc)
662
self._unstacked_provider.disable_cache()
664
# These depend on the actual remote format, so force them off for
665
# maximum compatibility. XXX: In future these should depend on the
666
# remote repository instance, but this is irrelevant until we perform
667
# reconcile via an RPC call.
668
self._reconcile_does_inventory_gc = False
669
self._reconcile_fixes_text_parents = False
670
self._reconcile_backsup_inventory = False
671
self.base = self.bzrdir.transport.base
672
# Additional places to query for data.
673
self._fallback_repositories = []
676
return "%s(%s)" % (self.__class__.__name__, self.base)
680
def abort_write_group(self, suppress_errors=False):
681
"""Complete a write group on the decorated repository.
683
Smart methods perform operations in a single step so this API
684
is not really applicable except as a compatibility thunk
685
for older plugins that don't use e.g. the CommitBuilder
688
:param suppress_errors: see Repository.abort_write_group.
691
return self._real_repository.abort_write_group(
692
suppress_errors=suppress_errors)
696
"""Decorate the real repository for now.
698
In the long term a full blown network facility is needed to avoid
699
creating a real repository object locally.
702
return self._real_repository.chk_bytes
704
def commit_write_group(self):
705
"""Complete a write group on the decorated repository.
707
Smart methods perform operations in a single step so this API
708
is not really applicable except as a compatibility thunk
709
for older plugins that don't use e.g. the CommitBuilder
713
return self._real_repository.commit_write_group()
715
def resume_write_group(self, tokens):
717
return self._real_repository.resume_write_group(tokens)
719
def suspend_write_group(self):
721
return self._real_repository.suspend_write_group()
723
def get_missing_parent_inventories(self, check_for_missing_texts=True):
725
return self._real_repository.get_missing_parent_inventories(
726
check_for_missing_texts=check_for_missing_texts)
728
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
730
return self._real_repository.get_rev_id_for_revno(
733
def get_rev_id_for_revno(self, revno, known_pair):
734
"""See Repository.get_rev_id_for_revno."""
735
path = self.bzrdir._path_for_remote_call(self._client)
737
if self._client._medium._is_remote_before((1, 17)):
738
return self._get_rev_id_for_revno_vfs(revno, known_pair)
739
response = self._call(
740
'Repository.get_rev_id_for_revno', path, revno, known_pair)
741
except errors.UnknownSmartMethod:
742
self._client._medium._remember_remote_is_before((1, 17))
743
return self._get_rev_id_for_revno_vfs(revno, known_pair)
744
if response[0] == 'ok':
745
return True, response[1]
746
elif response[0] == 'history-incomplete':
747
known_pair = response[1:3]
748
for fallback in self._fallback_repositories:
749
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
754
# Not found in any fallbacks
755
return False, known_pair
757
raise errors.UnexpectedSmartServerResponse(response)
759
def _ensure_real(self):
760
"""Ensure that there is a _real_repository set.
762
Used before calls to self._real_repository.
764
Note that _ensure_real causes many roundtrips to the server which are
765
not desirable, and prevents the use of smart one-roundtrip RPC's to
766
perform complex operations (such as accessing parent data, streaming
767
revisions etc). Adding calls to _ensure_real should only be done when
768
bringing up new functionality, adding fallbacks for smart methods that
769
require a fallback path, and never to replace an existing smart method
770
invocation. If in doubt chat to the bzr network team.
772
if self._real_repository is None:
773
if 'hpssvfs' in debug.debug_flags:
775
warning('VFS Repository access triggered\n%s',
776
''.join(traceback.format_stack()))
777
self._unstacked_provider.missing_keys.clear()
778
self.bzrdir._ensure_real()
779
self._set_real_repository(
780
self.bzrdir._real_bzrdir.open_repository())
782
def _translate_error(self, err, **context):
783
self.bzrdir._translate_error(err, repository=self, **context)
785
def find_text_key_references(self):
786
"""Find the text key references within the repository.
788
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
789
revision_ids. Each altered file-ids has the exact revision_ids that
790
altered it listed explicitly.
791
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
792
to whether they were referred to by the inventory of the
793
revision_id that they contain. The inventory texts from all present
794
revision ids are assessed to generate this report.
797
return self._real_repository.find_text_key_references()
799
def _generate_text_key_index(self):
800
"""Generate a new text key index for the repository.
802
This is an expensive function that will take considerable time to run.
804
:return: A dict mapping (file_id, revision_id) tuples to a list of
805
parents, also (file_id, revision_id) tuples.
808
return self._real_repository._generate_text_key_index()
810
def _get_revision_graph(self, revision_id):
811
"""Private method for using with old (< 1.2) servers to fallback."""
812
if revision_id is None:
814
elif revision.is_null(revision_id):
817
path = self.bzrdir._path_for_remote_call(self._client)
818
response = self._call_expecting_body(
819
'Repository.get_revision_graph', path, revision_id)
820
response_tuple, response_handler = response
821
if response_tuple[0] != 'ok':
822
raise errors.UnexpectedSmartServerResponse(response_tuple)
823
coded = response_handler.read_body_bytes()
825
# no revisions in this repository!
827
lines = coded.split('\n')
830
d = tuple(line.split())
831
revision_graph[d[0]] = d[1:]
833
return revision_graph
836
"""See Repository._get_sink()."""
837
return RemoteStreamSink(self)
839
def _get_source(self, to_format):
840
"""Return a source for streaming from this repository."""
841
return RemoteStreamSource(self, to_format)
844
def has_revision(self, revision_id):
845
"""True if this repository has a copy of the revision."""
846
# Copy of bzrlib.repository.Repository.has_revision
847
return revision_id in self.has_revisions((revision_id,))
850
def has_revisions(self, revision_ids):
851
"""Probe to find out the presence of multiple revisions.
853
:param revision_ids: An iterable of revision_ids.
854
:return: A set of the revision_ids that were present.
856
# Copy of bzrlib.repository.Repository.has_revisions
857
parent_map = self.get_parent_map(revision_ids)
858
result = set(parent_map)
859
if _mod_revision.NULL_REVISION in revision_ids:
860
result.add(_mod_revision.NULL_REVISION)
863
def _has_same_fallbacks(self, other_repo):
864
"""Returns true if the repositories have the same fallbacks."""
865
# XXX: copied from Repository; it should be unified into a base class
866
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
867
my_fb = self._fallback_repositories
868
other_fb = other_repo._fallback_repositories
869
if len(my_fb) != len(other_fb):
871
for f, g in zip(my_fb, other_fb):
872
if not f.has_same_location(g):
876
def has_same_location(self, other):
877
# TODO: Move to RepositoryBase and unify with the regular Repository
878
# one; unfortunately the tests rely on slightly different behaviour at
879
# present -- mbp 20090710
880
return (self.__class__ is other.__class__ and
881
self.bzrdir.transport.base == other.bzrdir.transport.base)
883
def get_graph(self, other_repository=None):
884
"""Return the graph for this repository format"""
885
parents_provider = self._make_parents_provider(other_repository)
886
return graph.Graph(parents_provider)
888
def gather_stats(self, revid=None, committers=None):
889
"""See Repository.gather_stats()."""
890
path = self.bzrdir._path_for_remote_call(self._client)
891
# revid can be None to indicate no revisions, not just NULL_REVISION
892
if revid is None or revision.is_null(revid):
896
if committers is None or not committers:
897
fmt_committers = 'no'
899
fmt_committers = 'yes'
900
response_tuple, response_handler = self._call_expecting_body(
901
'Repository.gather_stats', path, fmt_revid, fmt_committers)
902
if response_tuple[0] != 'ok':
903
raise errors.UnexpectedSmartServerResponse(response_tuple)
905
body = response_handler.read_body_bytes()
907
for line in body.split('\n'):
910
key, val_text = line.split(':')
911
if key in ('revisions', 'size', 'committers'):
912
result[key] = int(val_text)
913
elif key in ('firstrev', 'latestrev'):
914
values = val_text.split(' ')[1:]
915
result[key] = (float(values[0]), long(values[1]))
919
def find_branches(self, using=False):
920
"""See Repository.find_branches()."""
921
# should be an API call to the server.
923
return self._real_repository.find_branches(using=using)
925
def get_physical_lock_status(self):
926
"""See Repository.get_physical_lock_status()."""
927
# should be an API call to the server.
929
return self._real_repository.get_physical_lock_status()
931
def is_in_write_group(self):
932
"""Return True if there is an open write group.
934
write groups are only applicable locally for the smart server..
936
if self._real_repository:
937
return self._real_repository.is_in_write_group()
940
return self._lock_count >= 1
943
"""See Repository.is_shared()."""
944
path = self.bzrdir._path_for_remote_call(self._client)
945
response = self._call('Repository.is_shared', path)
946
if response[0] not in ('yes', 'no'):
947
raise SmartProtocolError('unexpected response code %s' % (response,))
948
return response[0] == 'yes'
950
def is_write_locked(self):
951
return self._lock_mode == 'w'
954
# wrong eventually - want a local lock cache context
955
if not self._lock_mode:
957
self._lock_mode = 'r'
959
self._unstacked_provider.enable_cache(cache_misses=True)
960
if self._real_repository is not None:
961
self._real_repository.lock_read()
962
for repo in self._fallback_repositories:
965
self._lock_count += 1
967
def _remote_lock_write(self, token):
968
path = self.bzrdir._path_for_remote_call(self._client)
971
err_context = {'token': token}
972
response = self._call('Repository.lock_write', path, token,
974
if response[0] == 'ok':
978
raise errors.UnexpectedSmartServerResponse(response)
980
def lock_write(self, token=None, _skip_rpc=False):
981
if not self._lock_mode:
984
if self._lock_token is not None:
985
if token != self._lock_token:
986
raise errors.TokenMismatch(token, self._lock_token)
987
self._lock_token = token
989
self._lock_token = self._remote_lock_write(token)
990
# if self._lock_token is None, then this is something like packs or
991
# svn where we don't get to lock the repo, or a weave style repository
992
# where we cannot lock it over the wire and attempts to do so will
994
if self._real_repository is not None:
995
self._real_repository.lock_write(token=self._lock_token)
996
if token is not None:
997
self._leave_lock = True
999
self._leave_lock = False
1000
self._lock_mode = 'w'
1001
self._lock_count = 1
1002
cache_misses = self._real_repository is None
1003
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1004
for repo in self._fallback_repositories:
1005
# Writes don't affect fallback repos
1007
elif self._lock_mode == 'r':
1008
raise errors.ReadOnlyError(self)
1010
self._lock_count += 1
1011
return self._lock_token or None
1013
def leave_lock_in_place(self):
1014
if not self._lock_token:
1015
raise NotImplementedError(self.leave_lock_in_place)
1016
self._leave_lock = True
1018
def dont_leave_lock_in_place(self):
1019
if not self._lock_token:
1020
raise NotImplementedError(self.dont_leave_lock_in_place)
1021
self._leave_lock = False
1023
def _set_real_repository(self, repository):
1024
"""Set the _real_repository for this repository.
1026
:param repository: The repository to fallback to for non-hpss
1027
implemented operations.
1029
if self._real_repository is not None:
1030
# Replacing an already set real repository.
1031
# We cannot do this [currently] if the repository is locked -
1032
# synchronised state might be lost.
1033
if self.is_locked():
1034
raise AssertionError('_real_repository is already set')
1035
if isinstance(repository, RemoteRepository):
1036
raise AssertionError()
1037
self._real_repository = repository
1038
# three code paths happen here:
1039
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1040
# up stacking. In this case self._fallback_repositories is [], and the
1041
# real repo is already setup. Preserve the real repo and
1042
# RemoteRepository.add_fallback_repository will avoid adding
1044
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1045
# ensure_real is triggered from a branch, the real repository to
1046
# set already has a matching list with separate instances, but
1047
# as they are also RemoteRepositories we don't worry about making the
1048
# lists be identical.
1049
# 3) new servers, RemoteRepository.ensure_real is triggered before
1050
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1051
# and need to populate it.
1052
if (self._fallback_repositories and
1053
len(self._real_repository._fallback_repositories) !=
1054
len(self._fallback_repositories)):
1055
if len(self._real_repository._fallback_repositories):
1056
raise AssertionError(
1057
"cannot cleanly remove existing _fallback_repositories")
1058
for fb in self._fallback_repositories:
1059
self._real_repository.add_fallback_repository(fb)
1060
if self._lock_mode == 'w':
1061
# if we are already locked, the real repository must be able to
1062
# acquire the lock with our token.
1063
self._real_repository.lock_write(self._lock_token)
1064
elif self._lock_mode == 'r':
1065
self._real_repository.lock_read()
1067
def start_write_group(self):
1068
"""Start a write group on the decorated repository.
1070
Smart methods perform operations in a single step so this API
1071
is not really applicable except as a compatibility thunk
1072
for older plugins that don't use e.g. the CommitBuilder
1076
return self._real_repository.start_write_group()
1078
def _unlock(self, token):
1079
path = self.bzrdir._path_for_remote_call(self._client)
1081
# with no token the remote repository is not persistently locked.
1083
err_context = {'token': token}
1084
response = self._call('Repository.unlock', path, token,
1086
if response == ('ok',):
1089
raise errors.UnexpectedSmartServerResponse(response)
1091
@only_raises(errors.LockNotHeld, errors.LockBroken)
1093
if not self._lock_count:
1094
return lock.cant_unlock_not_held(self)
1095
self._lock_count -= 1
1096
if self._lock_count > 0:
1098
self._unstacked_provider.disable_cache()
1099
old_mode = self._lock_mode
1100
self._lock_mode = None
1102
# The real repository is responsible at present for raising an
1103
# exception if it's in an unfinished write group. However, it
1104
# normally will *not* actually remove the lock from disk - that's
1105
# done by the server on receiving the Repository.unlock call.
1106
# This is just to let the _real_repository stay up to date.
1107
if self._real_repository is not None:
1108
self._real_repository.unlock()
1110
# The rpc-level lock should be released even if there was a
1111
# problem releasing the vfs-based lock.
1113
# Only write-locked repositories need to make a remote method
1114
# call to perform the unlock.
1115
old_token = self._lock_token
1116
self._lock_token = None
1117
if not self._leave_lock:
1118
self._unlock(old_token)
1119
# Fallbacks are always 'lock_read()' so we don't pay attention to
1121
for repo in self._fallback_repositories:
1124
def break_lock(self):
1125
# should hand off to the network
1127
return self._real_repository.break_lock()
1129
def _get_tarball(self, compression):
1130
"""Return a TemporaryFile containing a repository tarball.
1132
Returns None if the server does not support sending tarballs.
1135
path = self.bzrdir._path_for_remote_call(self._client)
1137
response, protocol = self._call_expecting_body(
1138
'Repository.tarball', path, compression)
1139
except errors.UnknownSmartMethod:
1140
protocol.cancel_read_body()
1142
if response[0] == 'ok':
1143
# Extract the tarball and return it
1144
t = tempfile.NamedTemporaryFile()
1145
# TODO: rpc layer should read directly into it...
1146
t.write(protocol.read_body_bytes())
1149
raise errors.UnexpectedSmartServerResponse(response)
1151
def sprout(self, to_bzrdir, revision_id=None):
1152
# TODO: Option to control what format is created?
1154
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1156
dest_repo.fetch(self, revision_id=revision_id)
1159
### These methods are just thin shims to the VFS object for now.
1161
def revision_tree(self, revision_id):
1163
return self._real_repository.revision_tree(revision_id)
1165
def get_serializer_format(self):
1167
return self._real_repository.get_serializer_format()
1169
def get_commit_builder(self, branch, parents, config, timestamp=None,
1170
timezone=None, committer=None, revprops=None,
1172
# FIXME: It ought to be possible to call this without immediately
1173
# triggering _ensure_real. For now it's the easiest thing to do.
1175
real_repo = self._real_repository
1176
builder = real_repo.get_commit_builder(branch, parents,
1177
config, timestamp=timestamp, timezone=timezone,
1178
committer=committer, revprops=revprops, revision_id=revision_id)
1181
def add_fallback_repository(self, repository):
1182
"""Add a repository to use for looking up data not held locally.
1184
:param repository: A repository.
1186
if not self._format.supports_external_lookups:
1187
raise errors.UnstackableRepositoryFormat(
1188
self._format.network_name(), self.base)
1189
# We need to accumulate additional repositories here, to pass them in
1192
if self.is_locked():
1193
# We will call fallback.unlock() when we transition to the unlocked
1194
# state, so always add a lock here. If a caller passes us a locked
1195
# repository, they are responsible for unlocking it later.
1196
repository.lock_read()
1197
self._fallback_repositories.append(repository)
1198
# If self._real_repository was parameterised already (e.g. because a
1199
# _real_branch had its get_stacked_on_url method called), then the
1200
# repository to be added may already be in the _real_repositories list.
1201
if self._real_repository is not None:
1202
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1203
self._real_repository._fallback_repositories]
1204
if repository.bzrdir.root_transport.base not in fallback_locations:
1205
self._real_repository.add_fallback_repository(repository)
1207
def add_inventory(self, revid, inv, parents):
1209
return self._real_repository.add_inventory(revid, inv, parents)
1211
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1214
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1215
delta, new_revision_id, parents)
1217
def add_revision(self, rev_id, rev, inv=None, config=None):
1219
return self._real_repository.add_revision(
1220
rev_id, rev, inv=inv, config=config)
1223
def get_inventory(self, revision_id):
1225
return self._real_repository.get_inventory(revision_id)
1227
def iter_inventories(self, revision_ids, ordering=None):
1229
return self._real_repository.iter_inventories(revision_ids, ordering)
1232
def get_revision(self, revision_id):
1234
return self._real_repository.get_revision(revision_id)
1236
def get_transaction(self):
1238
return self._real_repository.get_transaction()
1241
def clone(self, a_bzrdir, revision_id=None):
1243
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1245
def make_working_trees(self):
1246
"""See Repository.make_working_trees"""
1248
return self._real_repository.make_working_trees()
1250
def refresh_data(self):
1251
"""Re-read any data needed to to synchronise with disk.
1253
This method is intended to be called after another repository instance
1254
(such as one used by a smart server) has inserted data into the
1255
repository. It may not be called during a write group, but may be
1256
called at any other time.
1258
if self.is_in_write_group():
1259
raise errors.InternalBzrError(
1260
"May not refresh_data while in a write group.")
1261
if self._real_repository is not None:
1262
self._real_repository.refresh_data()
1264
def revision_ids_to_search_result(self, result_set):
1265
"""Convert a set of revision ids to a graph SearchResult."""
1266
result_parents = set()
1267
for parents in self.get_graph().get_parent_map(
1268
result_set).itervalues():
1269
result_parents.update(parents)
1270
included_keys = result_set.intersection(result_parents)
1271
start_keys = result_set.difference(included_keys)
1272
exclude_keys = result_parents.difference(result_set)
1273
result = graph.SearchResult(start_keys, exclude_keys,
1274
len(result_set), result_set)
1278
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1279
"""Return the revision ids that other has that this does not.
1281
These are returned in topological order.
1283
revision_id: only return revision ids included by revision_id.
1285
return repository.InterRepository.get(
1286
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1288
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1290
# No base implementation to use as RemoteRepository is not a subclass
1291
# of Repository; so this is a copy of Repository.fetch().
1292
if fetch_spec is not None and revision_id is not None:
1293
raise AssertionError(
1294
"fetch_spec and revision_id are mutually exclusive.")
1295
if self.is_in_write_group():
1296
raise errors.InternalBzrError(
1297
"May not fetch while in a write group.")
1298
# fast path same-url fetch operations
1299
if (self.has_same_location(source)
1300
and fetch_spec is None
1301
and self._has_same_fallbacks(source)):
1302
# check that last_revision is in 'from' and then return a
1304
if (revision_id is not None and
1305
not revision.is_null(revision_id)):
1306
self.get_revision(revision_id)
1308
# if there is no specific appropriate InterRepository, this will get
1309
# the InterRepository base class, which raises an
1310
# IncompatibleRepositories when asked to fetch.
1311
inter = repository.InterRepository.get(source, self)
1312
return inter.fetch(revision_id=revision_id, pb=pb,
1313
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1315
def create_bundle(self, target, base, fileobj, format=None):
1317
self._real_repository.create_bundle(target, base, fileobj, format)
1320
def get_ancestry(self, revision_id, topo_sorted=True):
1322
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1324
def fileids_altered_by_revision_ids(self, revision_ids):
1326
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1328
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1330
return self._real_repository._get_versioned_file_checker(
1331
revisions, revision_versions_cache)
1333
def iter_files_bytes(self, desired_files):
1334
"""See Repository.iter_file_bytes.
1337
return self._real_repository.iter_files_bytes(desired_files)
1339
def get_parent_map(self, revision_ids):
1340
"""See bzrlib.Graph.get_parent_map()."""
1341
return self._make_parents_provider().get_parent_map(revision_ids)
1343
def _get_parent_map_rpc(self, keys):
1344
"""Helper for get_parent_map that performs the RPC."""
1345
medium = self._client._medium
1346
if medium._is_remote_before((1, 2)):
1347
# We already found out that the server can't understand
1348
# Repository.get_parent_map requests, so just fetch the whole
1351
# Note that this reads the whole graph, when only some keys are
1352
# wanted. On this old server there's no way (?) to get them all
1353
# in one go, and the user probably will have seen a warning about
1354
# the server being old anyhow.
1355
rg = self._get_revision_graph(None)
1356
# There is an API discrepancy between get_parent_map and
1357
# get_revision_graph. Specifically, a "key:()" pair in
1358
# get_revision_graph just means a node has no parents. For
1359
# "get_parent_map" it means the node is a ghost. So fix up the
1360
# graph to correct this.
1361
# https://bugs.launchpad.net/bzr/+bug/214894
1362
# There is one other "bug" which is that ghosts in
1363
# get_revision_graph() are not returned at all. But we won't worry
1364
# about that for now.
1365
for node_id, parent_ids in rg.iteritems():
1366
if parent_ids == ():
1367
rg[node_id] = (NULL_REVISION,)
1368
rg[NULL_REVISION] = ()
1373
raise ValueError('get_parent_map(None) is not valid')
1374
if NULL_REVISION in keys:
1375
keys.discard(NULL_REVISION)
1376
found_parents = {NULL_REVISION:()}
1378
return found_parents
1381
# TODO(Needs analysis): We could assume that the keys being requested
1382
# from get_parent_map are in a breadth first search, so typically they
1383
# will all be depth N from some common parent, and we don't have to
1384
# have the server iterate from the root parent, but rather from the
1385
# keys we're searching; and just tell the server the keyspace we
1386
# already have; but this may be more traffic again.
1388
# Transform self._parents_map into a search request recipe.
1389
# TODO: Manage this incrementally to avoid covering the same path
1390
# repeatedly. (The server will have to on each request, but the less
1391
# work done the better).
1393
# Negative caching notes:
1394
# new server sends missing when a request including the revid
1395
# 'include-missing:' is present in the request.
1396
# missing keys are serialised as missing:X, and we then call
1397
# provider.note_missing(X) for-all X
1398
parents_map = self._unstacked_provider.get_cached_map()
1399
if parents_map is None:
1400
# Repository is not locked, so there's no cache.
1402
# start_set is all the keys in the cache
1403
start_set = set(parents_map)
1404
# result set is all the references to keys in the cache
1405
result_parents = set()
1406
for parents in parents_map.itervalues():
1407
result_parents.update(parents)
1408
stop_keys = result_parents.difference(start_set)
1409
# We don't need to send ghosts back to the server as a position to
1411
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1412
key_count = len(parents_map)
1413
if (NULL_REVISION in result_parents
1414
and NULL_REVISION in self._unstacked_provider.missing_keys):
1415
# If we pruned NULL_REVISION from the stop_keys because it's also
1416
# in our cache of "missing" keys we need to increment our key count
1417
# by 1, because the reconsitituted SearchResult on the server will
1418
# still consider NULL_REVISION to be an included key.
1420
included_keys = start_set.intersection(result_parents)
1421
start_set.difference_update(included_keys)
1422
recipe = ('manual', start_set, stop_keys, key_count)
1423
body = self._serialise_search_recipe(recipe)
1424
path = self.bzrdir._path_for_remote_call(self._client)
1426
if type(key) is not str:
1428
"key %r not a plain string" % (key,))
1429
verb = 'Repository.get_parent_map'
1430
args = (path, 'include-missing:') + tuple(keys)
1432
response = self._call_with_body_bytes_expecting_body(
1434
except errors.UnknownSmartMethod:
1435
# Server does not support this method, so get the whole graph.
1436
# Worse, we have to force a disconnection, because the server now
1437
# doesn't realise it has a body on the wire to consume, so the
1438
# only way to recover is to abandon the connection.
1440
'Server is too old for fast get_parent_map, reconnecting. '
1441
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1443
# To avoid having to disconnect repeatedly, we keep track of the
1444
# fact the server doesn't understand remote methods added in 1.2.
1445
medium._remember_remote_is_before((1, 2))
1446
# Recurse just once and we should use the fallback code.
1447
return self._get_parent_map_rpc(keys)
1448
response_tuple, response_handler = response
1449
if response_tuple[0] not in ['ok']:
1450
response_handler.cancel_read_body()
1451
raise errors.UnexpectedSmartServerResponse(response_tuple)
1452
if response_tuple[0] == 'ok':
1453
coded = bz2.decompress(response_handler.read_body_bytes())
1455
# no revisions found
1457
lines = coded.split('\n')
1460
d = tuple(line.split())
1462
revision_graph[d[0]] = d[1:]
1465
if d[0].startswith('missing:'):
1467
self._unstacked_provider.note_missing_key(revid)
1469
# no parents - so give the Graph result
1471
revision_graph[d[0]] = (NULL_REVISION,)
1472
return revision_graph
1475
def get_signature_text(self, revision_id):
1477
return self._real_repository.get_signature_text(revision_id)
1480
def get_inventory_xml(self, revision_id):
1482
return self._real_repository.get_inventory_xml(revision_id)
1484
def deserialise_inventory(self, revision_id, xml):
1486
return self._real_repository.deserialise_inventory(revision_id, xml)
1488
def reconcile(self, other=None, thorough=False):
1490
return self._real_repository.reconcile(other=other, thorough=thorough)
1492
def all_revision_ids(self):
1494
return self._real_repository.all_revision_ids()
1497
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1499
return self._real_repository.get_deltas_for_revisions(revisions,
1500
specific_fileids=specific_fileids)
1503
def get_revision_delta(self, revision_id, specific_fileids=None):
1505
return self._real_repository.get_revision_delta(revision_id,
1506
specific_fileids=specific_fileids)
1509
def revision_trees(self, revision_ids):
1511
return self._real_repository.revision_trees(revision_ids)
1514
def get_revision_reconcile(self, revision_id):
1516
return self._real_repository.get_revision_reconcile(revision_id)
1519
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1521
return self._real_repository.check(revision_ids=revision_ids,
1522
callback_refs=callback_refs, check_repo=check_repo)
1524
def copy_content_into(self, destination, revision_id=None):
1526
return self._real_repository.copy_content_into(
1527
destination, revision_id=revision_id)
1529
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1530
# get a tarball of the remote repository, and copy from that into the
1532
from bzrlib import osutils
1534
# TODO: Maybe a progress bar while streaming the tarball?
1535
note("Copying repository content as tarball...")
1536
tar_file = self._get_tarball('bz2')
1537
if tar_file is None:
1539
destination = to_bzrdir.create_repository()
1541
tar = tarfile.open('repository', fileobj=tar_file,
1543
tmpdir = osutils.mkdtemp()
1545
_extract_tar(tar, tmpdir)
1546
tmp_bzrdir = BzrDir.open(tmpdir)
1547
tmp_repo = tmp_bzrdir.open_repository()
1548
tmp_repo.copy_content_into(destination, revision_id)
1550
osutils.rmtree(tmpdir)
1554
# TODO: Suggestion from john: using external tar is much faster than
1555
# python's tarfile library, but it may not work on windows.
1558
def inventories(self):
1559
"""Decorate the real repository for now.
1561
In the long term a full blown network facility is needed to
1562
avoid creating a real repository object locally.
1565
return self._real_repository.inventories
1568
def pack(self, hint=None):
1569
"""Compress the data within the repository.
1571
This is not currently implemented within the smart server.
1574
return self._real_repository.pack(hint=hint)
1577
def revisions(self):
1578
"""Decorate the real repository for now.
1580
In the short term this should become a real object to intercept graph
1583
In the long term a full blown network facility is needed.
1586
return self._real_repository.revisions
1588
def set_make_working_trees(self, new_value):
1590
new_value_str = "True"
1592
new_value_str = "False"
1593
path = self.bzrdir._path_for_remote_call(self._client)
1595
response = self._call(
1596
'Repository.set_make_working_trees', path, new_value_str)
1597
except errors.UnknownSmartMethod:
1599
self._real_repository.set_make_working_trees(new_value)
1601
if response[0] != 'ok':
1602
raise errors.UnexpectedSmartServerResponse(response)
1605
def signatures(self):
1606
"""Decorate the real repository for now.
1608
In the long term a full blown network facility is needed to avoid
1609
creating a real repository object locally.
1612
return self._real_repository.signatures
1615
def sign_revision(self, revision_id, gpg_strategy):
1617
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1621
"""Decorate the real repository for now.
1623
In the long term a full blown network facility is needed to avoid
1624
creating a real repository object locally.
1627
return self._real_repository.texts
1630
def get_revisions(self, revision_ids):
1632
return self._real_repository.get_revisions(revision_ids)
1634
def supports_rich_root(self):
1635
return self._format.rich_root_data
1637
def iter_reverse_revision_history(self, revision_id):
1639
return self._real_repository.iter_reverse_revision_history(revision_id)
1642
def _serializer(self):
1643
return self._format._serializer
1645
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1647
return self._real_repository.store_revision_signature(
1648
gpg_strategy, plaintext, revision_id)
1650
def add_signature_text(self, revision_id, signature):
1652
return self._real_repository.add_signature_text(revision_id, signature)
1654
def has_signature_for_revision_id(self, revision_id):
1656
return self._real_repository.has_signature_for_revision_id(revision_id)
1658
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1660
return self._real_repository.item_keys_introduced_by(revision_ids,
1661
_files_pb=_files_pb)
1663
def revision_graph_can_have_wrong_parents(self):
1664
# The answer depends on the remote repo format.
1666
return self._real_repository.revision_graph_can_have_wrong_parents()
1668
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1670
return self._real_repository._find_inconsistent_revision_parents(
1673
def _check_for_inconsistent_revision_parents(self):
1675
return self._real_repository._check_for_inconsistent_revision_parents()
1677
def _make_parents_provider(self, other=None):
1678
providers = [self._unstacked_provider]
1679
if other is not None:
1680
providers.insert(0, other)
1681
providers.extend(r._make_parents_provider() for r in
1682
self._fallback_repositories)
1683
return graph.StackedParentsProvider(providers)
1685
def _serialise_search_recipe(self, recipe):
1686
"""Serialise a graph search recipe.
1688
:param recipe: A search recipe (start, stop, count).
1689
:return: Serialised bytes.
1691
start_keys = ' '.join(recipe[1])
1692
stop_keys = ' '.join(recipe[2])
1693
count = str(recipe[3])
1694
return '\n'.join((start_keys, stop_keys, count))
1696
def _serialise_search_result(self, search_result):
1697
if isinstance(search_result, graph.PendingAncestryResult):
1698
parts = ['ancestry-of']
1699
parts.extend(search_result.heads)
1701
recipe = search_result.get_recipe()
1702
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1703
return '\n'.join(parts)
1706
path = self.bzrdir._path_for_remote_call(self._client)
1708
response = self._call('PackRepository.autopack', path)
1709
except errors.UnknownSmartMethod:
1711
self._real_repository._pack_collection.autopack()
1714
if response[0] != 'ok':
1715
raise errors.UnexpectedSmartServerResponse(response)
1718
class RemoteStreamSink(repository.StreamSink):
1720
def _insert_real(self, stream, src_format, resume_tokens):
1721
self.target_repo._ensure_real()
1722
sink = self.target_repo._real_repository._get_sink()
1723
result = sink.insert_stream(stream, src_format, resume_tokens)
1725
self.target_repo.autopack()
1728
def insert_stream(self, stream, src_format, resume_tokens):
1729
target = self.target_repo
1730
target._unstacked_provider.missing_keys.clear()
1731
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1732
if target._lock_token:
1733
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1734
lock_args = (target._lock_token or '',)
1736
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1738
client = target._client
1739
medium = client._medium
1740
path = target.bzrdir._path_for_remote_call(client)
1741
# Probe for the verb to use with an empty stream before sending the
1742
# real stream to it. We do this both to avoid the risk of sending a
1743
# large request that is then rejected, and because we don't want to
1744
# implement a way to buffer, rewind, or restart the stream.
1746
for verb, required_version in candidate_calls:
1747
if medium._is_remote_before(required_version):
1750
# We've already done the probing (and set _is_remote_before) on
1751
# a previous insert.
1754
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1756
response = client.call_with_body_stream(
1757
(verb, path, '') + lock_args, byte_stream)
1758
except errors.UnknownSmartMethod:
1759
medium._remember_remote_is_before(required_version)
1765
return self._insert_real(stream, src_format, resume_tokens)
1766
self._last_inv_record = None
1767
self._last_substream = None
1768
if required_version < (1, 19):
1769
# Remote side doesn't support inventory deltas. Wrap the stream to
1770
# make sure we don't send any. If the stream contains inventory
1771
# deltas we'll interrupt the smart insert_stream request and
1773
stream = self._stop_stream_if_inventory_delta(stream)
1774
byte_stream = smart_repo._stream_to_byte_stream(
1776
resume_tokens = ' '.join(resume_tokens)
1777
response = client.call_with_body_stream(
1778
(verb, path, resume_tokens) + lock_args, byte_stream)
1779
if response[0][0] not in ('ok', 'missing-basis'):
1780
raise errors.UnexpectedSmartServerResponse(response)
1781
if self._last_substream is not None:
1782
# The stream included an inventory-delta record, but the remote
1783
# side isn't new enough to support them. So we need to send the
1784
# rest of the stream via VFS.
1785
self.target_repo.refresh_data()
1786
return self._resume_stream_with_vfs(response, src_format)
1787
if response[0][0] == 'missing-basis':
1788
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1789
resume_tokens = tokens
1790
return resume_tokens, set(missing_keys)
1792
self.target_repo.refresh_data()
1795
def _resume_stream_with_vfs(self, response, src_format):
1796
"""Resume sending a stream via VFS, first resending the record and
1797
substream that couldn't be sent via an insert_stream verb.
1799
if response[0][0] == 'missing-basis':
1800
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1801
# Ignore missing_keys, we haven't finished inserting yet
1804
def resume_substream():
1805
# Yield the substream that was interrupted.
1806
for record in self._last_substream:
1808
self._last_substream = None
1809
def resume_stream():
1810
# Finish sending the interrupted substream
1811
yield ('inventory-deltas', resume_substream())
1812
# Then simply continue sending the rest of the stream.
1813
for substream_kind, substream in self._last_stream:
1814
yield substream_kind, substream
1815
return self._insert_real(resume_stream(), src_format, tokens)
1817
def _stop_stream_if_inventory_delta(self, stream):
1818
"""Normally this just lets the original stream pass-through unchanged.
1820
However if any 'inventory-deltas' substream occurs it will stop
1821
streaming, and store the interrupted substream and stream in
1822
self._last_substream and self._last_stream so that the stream can be
1823
resumed by _resume_stream_with_vfs.
1826
stream_iter = iter(stream)
1827
for substream_kind, substream in stream_iter:
1828
if substream_kind == 'inventory-deltas':
1829
self._last_substream = substream
1830
self._last_stream = stream_iter
1833
yield substream_kind, substream
1836
class RemoteStreamSource(repository.StreamSource):
1837
"""Stream data from a remote server."""
1839
def get_stream(self, search):
1840
if (self.from_repository._fallback_repositories and
1841
self.to_format._fetch_order == 'topological'):
1842
return self._real_stream(self.from_repository, search)
1845
repos = [self.from_repository]
1851
repos.extend(repo._fallback_repositories)
1852
sources.append(repo)
1853
return self.missing_parents_chain(search, sources)
1855
def get_stream_for_missing_keys(self, missing_keys):
1856
self.from_repository._ensure_real()
1857
real_repo = self.from_repository._real_repository
1858
real_source = real_repo._get_source(self.to_format)
1859
return real_source.get_stream_for_missing_keys(missing_keys)
1861
def _real_stream(self, repo, search):
1862
"""Get a stream for search from repo.
1864
This never called RemoteStreamSource.get_stream, and is a heler
1865
for RemoteStreamSource._get_stream to allow getting a stream
1866
reliably whether fallback back because of old servers or trying
1867
to stream from a non-RemoteRepository (which the stacked support
1870
source = repo._get_source(self.to_format)
1871
if isinstance(source, RemoteStreamSource):
1873
source = repo._real_repository._get_source(self.to_format)
1874
return source.get_stream(search)
1876
def _get_stream(self, repo, search):
1877
"""Core worker to get a stream from repo for search.
1879
This is used by both get_stream and the stacking support logic. It
1880
deliberately gets a stream for repo which does not need to be
1881
self.from_repository. In the event that repo is not Remote, or
1882
cannot do a smart stream, a fallback is made to the generic
1883
repository._get_stream() interface, via self._real_stream.
1885
In the event of stacking, streams from _get_stream will not
1886
contain all the data for search - this is normal (see get_stream).
1888
:param repo: A repository.
1889
:param search: A search.
1891
# Fallbacks may be non-smart
1892
if not isinstance(repo, RemoteRepository):
1893
return self._real_stream(repo, search)
1894
client = repo._client
1895
medium = client._medium
1896
path = repo.bzrdir._path_for_remote_call(client)
1897
search_bytes = repo._serialise_search_result(search)
1898
args = (path, self.to_format.network_name())
1900
('Repository.get_stream_1.19', (1, 19)),
1901
('Repository.get_stream', (1, 13))]
1903
for verb, version in candidate_verbs:
1904
if medium._is_remote_before(version):
1907
response = repo._call_with_body_bytes_expecting_body(
1908
verb, args, search_bytes)
1909
except errors.UnknownSmartMethod:
1910
medium._remember_remote_is_before(version)
1912
response_tuple, response_handler = response
1916
return self._real_stream(repo, search)
1917
if response_tuple[0] != 'ok':
1918
raise errors.UnexpectedSmartServerResponse(response_tuple)
1919
byte_stream = response_handler.read_streamed_body()
1920
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1921
if src_format.network_name() != repo._format.network_name():
1922
raise AssertionError(
1923
"Mismatched RemoteRepository and stream src %r, %r" % (
1924
src_format.network_name(), repo._format.network_name()))
1927
def missing_parents_chain(self, search, sources):
1928
"""Chain multiple streams together to handle stacking.
1930
:param search: The overall search to satisfy with streams.
1931
:param sources: A list of Repository objects to query.
1933
self.from_serialiser = self.from_repository._format._serializer
1934
self.seen_revs = set()
1935
self.referenced_revs = set()
1936
# If there are heads in the search, or the key count is > 0, we are not
1938
while not search.is_empty() and len(sources) > 1:
1939
source = sources.pop(0)
1940
stream = self._get_stream(source, search)
1941
for kind, substream in stream:
1942
if kind != 'revisions':
1943
yield kind, substream
1945
yield kind, self.missing_parents_rev_handler(substream)
1946
search = search.refine(self.seen_revs, self.referenced_revs)
1947
self.seen_revs = set()
1948
self.referenced_revs = set()
1949
if not search.is_empty():
1950
for kind, stream in self._get_stream(sources[0], search):
1953
def missing_parents_rev_handler(self, substream):
1954
for content in substream:
1955
revision_bytes = content.get_bytes_as('fulltext')
1956
revision = self.from_serialiser.read_revision_from_string(
1958
self.seen_revs.add(content.key[-1])
1959
self.referenced_revs.update(revision.parent_ids)
1963
class RemoteBranchLockableFiles(LockableFiles):
1964
"""A 'LockableFiles' implementation that talks to a smart server.
1966
This is not a public interface class.
1969
def __init__(self, bzrdir, _client):
1970
self.bzrdir = bzrdir
1971
self._client = _client
1972
self._need_find_modes = True
1973
LockableFiles.__init__(
1974
self, bzrdir.get_branch_transport(None),
1975
'lock', lockdir.LockDir)
1977
def _find_modes(self):
1978
# RemoteBranches don't let the client set the mode of control files.
1979
self._dir_mode = None
1980
self._file_mode = None
1983
class RemoteBranchFormat(branch.BranchFormat):
1985
def __init__(self, network_name=None):
1986
super(RemoteBranchFormat, self).__init__()
1987
self._matchingbzrdir = RemoteBzrDirFormat()
1988
self._matchingbzrdir.set_branch_format(self)
1989
self._custom_format = None
1990
self._network_name = network_name
1992
def __eq__(self, other):
1993
return (isinstance(other, RemoteBranchFormat) and
1994
self.__dict__ == other.__dict__)
1996
def _ensure_real(self):
1997
if self._custom_format is None:
1998
self._custom_format = branch.network_format_registry.get(
2001
def get_format_description(self):
2002
return 'Remote BZR Branch'
2004
def network_name(self):
2005
return self._network_name
2007
def open(self, a_bzrdir, ignore_fallbacks=False):
2008
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2010
def _vfs_initialize(self, a_bzrdir):
2011
# Initialisation when using a local bzrdir object, or a non-vfs init
2012
# method is not available on the server.
2013
# self._custom_format is always set - the start of initialize ensures
2015
if isinstance(a_bzrdir, RemoteBzrDir):
2016
a_bzrdir._ensure_real()
2017
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2019
# We assume the bzrdir is parameterised; it may not be.
2020
result = self._custom_format.initialize(a_bzrdir)
2021
if (isinstance(a_bzrdir, RemoteBzrDir) and
2022
not isinstance(result, RemoteBranch)):
2023
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2026
def initialize(self, a_bzrdir):
2027
# 1) get the network name to use.
2028
if self._custom_format:
2029
network_name = self._custom_format.network_name()
2031
# Select the current bzrlib default and ask for that.
2032
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2033
reference_format = reference_bzrdir_format.get_branch_format()
2034
self._custom_format = reference_format
2035
network_name = reference_format.network_name()
2036
# Being asked to create on a non RemoteBzrDir:
2037
if not isinstance(a_bzrdir, RemoteBzrDir):
2038
return self._vfs_initialize(a_bzrdir)
2039
medium = a_bzrdir._client._medium
2040
if medium._is_remote_before((1, 13)):
2041
return self._vfs_initialize(a_bzrdir)
2042
# Creating on a remote bzr dir.
2043
# 2) try direct creation via RPC
2044
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2045
verb = 'BzrDir.create_branch'
2047
response = a_bzrdir._call(verb, path, network_name)
2048
except errors.UnknownSmartMethod:
2049
# Fallback - use vfs methods
2050
medium._remember_remote_is_before((1, 13))
2051
return self._vfs_initialize(a_bzrdir)
2052
if response[0] != 'ok':
2053
raise errors.UnexpectedSmartServerResponse(response)
2054
# Turn the response into a RemoteRepository object.
2055
format = RemoteBranchFormat(network_name=response[1])
2056
repo_format = response_tuple_to_repo_format(response[3:])
2057
if response[2] == '':
2058
repo_bzrdir = a_bzrdir
2060
repo_bzrdir = RemoteBzrDir(
2061
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2063
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2064
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2065
format=format, setup_stacking=False)
2066
# XXX: We know this is a new branch, so it must have revno 0, revid
2067
# NULL_REVISION. Creating the branch locked would make this be unable
2068
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2069
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2070
return remote_branch
2072
def make_tags(self, branch):
2074
return self._custom_format.make_tags(branch)
2076
def supports_tags(self):
2077
# Remote branches might support tags, but we won't know until we
2078
# access the real remote branch.
2080
return self._custom_format.supports_tags()
2082
def supports_stacking(self):
2084
return self._custom_format.supports_stacking()
2086
def supports_set_append_revisions_only(self):
2088
return self._custom_format.supports_set_append_revisions_only()
2091
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2092
"""Branch stored on a server accessed by HPSS RPC.
2094
At the moment most operations are mapped down to simple file operations.
2097
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2098
_client=None, format=None, setup_stacking=True):
2099
"""Create a RemoteBranch instance.
2101
:param real_branch: An optional local implementation of the branch
2102
format, usually accessing the data via the VFS.
2103
:param _client: Private parameter for testing.
2104
:param format: A RemoteBranchFormat object, None to create one
2105
automatically. If supplied it should have a network_name already
2107
:param setup_stacking: If True make an RPC call to determine the
2108
stacked (or not) status of the branch. If False assume the branch
2111
# We intentionally don't call the parent class's __init__, because it
2112
# will try to assign to self.tags, which is a property in this subclass.
2113
# And the parent's __init__ doesn't do much anyway.
2114
self.bzrdir = remote_bzrdir
2115
if _client is not None:
2116
self._client = _client
2118
self._client = remote_bzrdir._client
2119
self.repository = remote_repository
2120
if real_branch is not None:
2121
self._real_branch = real_branch
2122
# Give the remote repository the matching real repo.
2123
real_repo = self._real_branch.repository
2124
if isinstance(real_repo, RemoteRepository):
2125
real_repo._ensure_real()
2126
real_repo = real_repo._real_repository
2127
self.repository._set_real_repository(real_repo)
2128
# Give the branch the remote repository to let fast-pathing happen.
2129
self._real_branch.repository = self.repository
2131
self._real_branch = None
2132
# Fill out expected attributes of branch for bzrlib API users.
2133
self._clear_cached_state()
2134
self.base = self.bzrdir.root_transport.base
2135
self._control_files = None
2136
self._lock_mode = None
2137
self._lock_token = None
2138
self._repo_lock_token = None
2139
self._lock_count = 0
2140
self._leave_lock = False
2141
# Setup a format: note that we cannot call _ensure_real until all the
2142
# attributes above are set: This code cannot be moved higher up in this
2145
self._format = RemoteBranchFormat()
2146
if real_branch is not None:
2147
self._format._network_name = \
2148
self._real_branch._format.network_name()
2150
self._format = format
2151
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2152
# branch.open_branch method.
2153
self._real_ignore_fallbacks = not setup_stacking
2154
if not self._format._network_name:
2155
# Did not get from open_branchV2 - old server.
2157
self._format._network_name = \
2158
self._real_branch._format.network_name()
2159
self.tags = self._format.make_tags(self)
2160
# The base class init is not called, so we duplicate this:
2161
hooks = branch.Branch.hooks['open']
2164
self._is_stacked = False
2166
self._setup_stacking()
2168
def _setup_stacking(self):
2169
# configure stacking into the remote repository, by reading it from
2172
fallback_url = self.get_stacked_on_url()
2173
except (errors.NotStacked, errors.UnstackableBranchFormat,
2174
errors.UnstackableRepositoryFormat), e:
2176
self._is_stacked = True
2177
self._activate_fallback_location(fallback_url)
2179
def _get_config(self):
2180
return RemoteBranchConfig(self)
2182
def _get_real_transport(self):
2183
# if we try vfs access, return the real branch's vfs transport
2185
return self._real_branch._transport
2187
_transport = property(_get_real_transport)
2190
return "%s(%s)" % (self.__class__.__name__, self.base)
2194
def _ensure_real(self):
2195
"""Ensure that there is a _real_branch set.
2197
Used before calls to self._real_branch.
2199
if self._real_branch is None:
2200
if not vfs.vfs_enabled():
2201
raise AssertionError('smart server vfs must be enabled '
2202
'to use vfs implementation')
2203
self.bzrdir._ensure_real()
2204
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2205
ignore_fallbacks=self._real_ignore_fallbacks)
2206
if self.repository._real_repository is None:
2207
# Give the remote repository the matching real repo.
2208
real_repo = self._real_branch.repository
2209
if isinstance(real_repo, RemoteRepository):
2210
real_repo._ensure_real()
2211
real_repo = real_repo._real_repository
2212
self.repository._set_real_repository(real_repo)
2213
# Give the real branch the remote repository to let fast-pathing
2215
self._real_branch.repository = self.repository
2216
if self._lock_mode == 'r':
2217
self._real_branch.lock_read()
2218
elif self._lock_mode == 'w':
2219
self._real_branch.lock_write(token=self._lock_token)
2221
def _translate_error(self, err, **context):
2222
self.repository._translate_error(err, branch=self, **context)
2224
def _clear_cached_state(self):
2225
super(RemoteBranch, self)._clear_cached_state()
2226
if self._real_branch is not None:
2227
self._real_branch._clear_cached_state()
2229
def _clear_cached_state_of_remote_branch_only(self):
2230
"""Like _clear_cached_state, but doesn't clear the cache of
2233
This is useful when falling back to calling a method of
2234
self._real_branch that changes state. In that case the underlying
2235
branch changes, so we need to invalidate this RemoteBranch's cache of
2236
it. However, there's no need to invalidate the _real_branch's cache
2237
too, in fact doing so might harm performance.
2239
super(RemoteBranch, self)._clear_cached_state()
2242
def control_files(self):
2243
# Defer actually creating RemoteBranchLockableFiles until its needed,
2244
# because it triggers an _ensure_real that we otherwise might not need.
2245
if self._control_files is None:
2246
self._control_files = RemoteBranchLockableFiles(
2247
self.bzrdir, self._client)
2248
return self._control_files
2250
def _get_checkout_format(self):
2252
return self._real_branch._get_checkout_format()
2254
def get_physical_lock_status(self):
2255
"""See Branch.get_physical_lock_status()."""
2256
# should be an API call to the server, as branches must be lockable.
2258
return self._real_branch.get_physical_lock_status()
2260
def get_stacked_on_url(self):
2261
"""Get the URL this branch is stacked against.
2263
:raises NotStacked: If the branch is not stacked.
2264
:raises UnstackableBranchFormat: If the branch does not support
2266
:raises UnstackableRepositoryFormat: If the repository does not support
2270
# there may not be a repository yet, so we can't use
2271
# self._translate_error, so we can't use self._call either.
2272
response = self._client.call('Branch.get_stacked_on_url',
2273
self._remote_path())
2274
except errors.ErrorFromSmartServer, err:
2275
# there may not be a repository yet, so we can't call through
2276
# its _translate_error
2277
_translate_error(err, branch=self)
2278
except errors.UnknownSmartMethod, err:
2280
return self._real_branch.get_stacked_on_url()
2281
if response[0] != 'ok':
2282
raise errors.UnexpectedSmartServerResponse(response)
2285
def set_stacked_on_url(self, url):
2286
branch.Branch.set_stacked_on_url(self, url)
2288
self._is_stacked = False
2290
self._is_stacked = True
2292
def _vfs_get_tags_bytes(self):
2294
return self._real_branch._get_tags_bytes()
2296
def _get_tags_bytes(self):
2297
medium = self._client._medium
2298
if medium._is_remote_before((1, 13)):
2299
return self._vfs_get_tags_bytes()
2301
response = self._call('Branch.get_tags_bytes', self._remote_path())
2302
except errors.UnknownSmartMethod:
2303
medium._remember_remote_is_before((1, 13))
2304
return self._vfs_get_tags_bytes()
2307
def _vfs_set_tags_bytes(self, bytes):
2309
return self._real_branch._set_tags_bytes(bytes)
2311
def _set_tags_bytes(self, bytes):
2312
medium = self._client._medium
2313
if medium._is_remote_before((1, 18)):
2314
self._vfs_set_tags_bytes(bytes)
2318
self._remote_path(), self._lock_token, self._repo_lock_token)
2319
response = self._call_with_body_bytes(
2320
'Branch.set_tags_bytes', args, bytes)
2321
except errors.UnknownSmartMethod:
2322
medium._remember_remote_is_before((1, 18))
2323
self._vfs_set_tags_bytes(bytes)
2325
def lock_read(self):
2326
self.repository.lock_read()
2327
if not self._lock_mode:
2328
self._note_lock('r')
2329
self._lock_mode = 'r'
2330
self._lock_count = 1
2331
if self._real_branch is not None:
2332
self._real_branch.lock_read()
2334
self._lock_count += 1
2336
def _remote_lock_write(self, token):
2338
branch_token = repo_token = ''
2340
branch_token = token
2341
repo_token = self.repository.lock_write()
2342
self.repository.unlock()
2343
err_context = {'token': token}
2344
response = self._call(
2345
'Branch.lock_write', self._remote_path(), branch_token,
2346
repo_token or '', **err_context)
2347
if response[0] != 'ok':
2348
raise errors.UnexpectedSmartServerResponse(response)
2349
ok, branch_token, repo_token = response
2350
return branch_token, repo_token
2352
def lock_write(self, token=None):
2353
if not self._lock_mode:
2354
self._note_lock('w')
2355
# Lock the branch and repo in one remote call.
2356
remote_tokens = self._remote_lock_write(token)
2357
self._lock_token, self._repo_lock_token = remote_tokens
2358
if not self._lock_token:
2359
raise SmartProtocolError('Remote server did not return a token!')
2360
# Tell the self.repository object that it is locked.
2361
self.repository.lock_write(
2362
self._repo_lock_token, _skip_rpc=True)
2364
if self._real_branch is not None:
2365
self._real_branch.lock_write(token=self._lock_token)
2366
if token is not None:
2367
self._leave_lock = True
2369
self._leave_lock = False
2370
self._lock_mode = 'w'
2371
self._lock_count = 1
2372
elif self._lock_mode == 'r':
2373
raise errors.ReadOnlyTransaction
2375
if token is not None:
2376
# A token was given to lock_write, and we're relocking, so
2377
# check that the given token actually matches the one we
2379
if token != self._lock_token:
2380
raise errors.TokenMismatch(token, self._lock_token)
2381
self._lock_count += 1
2382
# Re-lock the repository too.
2383
self.repository.lock_write(self._repo_lock_token)
2384
return self._lock_token or None
2386
def _unlock(self, branch_token, repo_token):
2387
err_context = {'token': str((branch_token, repo_token))}
2388
response = self._call(
2389
'Branch.unlock', self._remote_path(), branch_token,
2390
repo_token or '', **err_context)
2391
if response == ('ok',):
2393
raise errors.UnexpectedSmartServerResponse(response)
2395
@only_raises(errors.LockNotHeld, errors.LockBroken)
2398
self._lock_count -= 1
2399
if not self._lock_count:
2400
self._clear_cached_state()
2401
mode = self._lock_mode
2402
self._lock_mode = None
2403
if self._real_branch is not None:
2404
if (not self._leave_lock and mode == 'w' and
2405
self._repo_lock_token):
2406
# If this RemoteBranch will remove the physical lock
2407
# for the repository, make sure the _real_branch
2408
# doesn't do it first. (Because the _real_branch's
2409
# repository is set to be the RemoteRepository.)
2410
self._real_branch.repository.leave_lock_in_place()
2411
self._real_branch.unlock()
2413
# Only write-locked branched need to make a remote method
2414
# call to perform the unlock.
2416
if not self._lock_token:
2417
raise AssertionError('Locked, but no token!')
2418
branch_token = self._lock_token
2419
repo_token = self._repo_lock_token
2420
self._lock_token = None
2421
self._repo_lock_token = None
2422
if not self._leave_lock:
2423
self._unlock(branch_token, repo_token)
2425
self.repository.unlock()
2427
def break_lock(self):
2429
return self._real_branch.break_lock()
2431
def leave_lock_in_place(self):
2432
if not self._lock_token:
2433
raise NotImplementedError(self.leave_lock_in_place)
2434
self._leave_lock = True
2436
def dont_leave_lock_in_place(self):
2437
if not self._lock_token:
2438
raise NotImplementedError(self.dont_leave_lock_in_place)
2439
self._leave_lock = False
2442
def get_rev_id(self, revno, history=None):
2444
return _mod_revision.NULL_REVISION
2445
last_revision_info = self.last_revision_info()
2446
ok, result = self.repository.get_rev_id_for_revno(
2447
revno, last_revision_info)
2450
missing_parent = result[1]
2451
# Either the revision named by the server is missing, or its parent
2452
# is. Call get_parent_map to determine which, so that we report a
2454
parent_map = self.repository.get_parent_map([missing_parent])
2455
if missing_parent in parent_map:
2456
missing_parent = parent_map[missing_parent]
2457
raise errors.RevisionNotPresent(missing_parent, self.repository)
2459
def _last_revision_info(self):
2460
response = self._call('Branch.last_revision_info', self._remote_path())
2461
if response[0] != 'ok':
2462
raise SmartProtocolError('unexpected response code %s' % (response,))
2463
revno = int(response[1])
2464
last_revision = response[2]
2465
return (revno, last_revision)
2467
def _gen_revision_history(self):
2468
"""See Branch._gen_revision_history()."""
2469
if self._is_stacked:
2471
return self._real_branch._gen_revision_history()
2472
response_tuple, response_handler = self._call_expecting_body(
2473
'Branch.revision_history', self._remote_path())
2474
if response_tuple[0] != 'ok':
2475
raise errors.UnexpectedSmartServerResponse(response_tuple)
2476
result = response_handler.read_body_bytes().split('\x00')
2481
def _remote_path(self):
2482
return self.bzrdir._path_for_remote_call(self._client)
2484
def _set_last_revision_descendant(self, revision_id, other_branch,
2485
allow_diverged=False, allow_overwrite_descendant=False):
2486
# This performs additional work to meet the hook contract; while its
2487
# undesirable, we have to synthesise the revno to call the hook, and
2488
# not calling the hook is worse as it means changes can't be prevented.
2489
# Having calculated this though, we can't just call into
2490
# set_last_revision_info as a simple call, because there is a set_rh
2491
# hook that some folk may still be using.
2492
old_revno, old_revid = self.last_revision_info()
2493
history = self._lefthand_history(revision_id)
2494
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2495
err_context = {'other_branch': other_branch}
2496
response = self._call('Branch.set_last_revision_ex',
2497
self._remote_path(), self._lock_token, self._repo_lock_token,
2498
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2500
self._clear_cached_state()
2501
if len(response) != 3 and response[0] != 'ok':
2502
raise errors.UnexpectedSmartServerResponse(response)
2503
new_revno, new_revision_id = response[1:]
2504
self._last_revision_info_cache = new_revno, new_revision_id
2505
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2506
if self._real_branch is not None:
2507
cache = new_revno, new_revision_id
2508
self._real_branch._last_revision_info_cache = cache
2510
def _set_last_revision(self, revision_id):
2511
old_revno, old_revid = self.last_revision_info()
2512
# This performs additional work to meet the hook contract; while its
2513
# undesirable, we have to synthesise the revno to call the hook, and
2514
# not calling the hook is worse as it means changes can't be prevented.
2515
# Having calculated this though, we can't just call into
2516
# set_last_revision_info as a simple call, because there is a set_rh
2517
# hook that some folk may still be using.
2518
history = self._lefthand_history(revision_id)
2519
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2520
self._clear_cached_state()
2521
response = self._call('Branch.set_last_revision',
2522
self._remote_path(), self._lock_token, self._repo_lock_token,
2524
if response != ('ok',):
2525
raise errors.UnexpectedSmartServerResponse(response)
2526
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2529
def set_revision_history(self, rev_history):
2530
# Send just the tip revision of the history; the server will generate
2531
# the full history from that. If the revision doesn't exist in this
2532
# branch, NoSuchRevision will be raised.
2533
if rev_history == []:
2536
rev_id = rev_history[-1]
2537
self._set_last_revision(rev_id)
2538
for hook in branch.Branch.hooks['set_rh']:
2539
hook(self, rev_history)
2540
self._cache_revision_history(rev_history)
2542
def _get_parent_location(self):
2543
medium = self._client._medium
2544
if medium._is_remote_before((1, 13)):
2545
return self._vfs_get_parent_location()
2547
response = self._call('Branch.get_parent', self._remote_path())
2548
except errors.UnknownSmartMethod:
2549
medium._remember_remote_is_before((1, 13))
2550
return self._vfs_get_parent_location()
2551
if len(response) != 1:
2552
raise errors.UnexpectedSmartServerResponse(response)
2553
parent_location = response[0]
2554
if parent_location == '':
2556
return parent_location
2558
def _vfs_get_parent_location(self):
2560
return self._real_branch._get_parent_location()
2562
def _set_parent_location(self, url):
2563
medium = self._client._medium
2564
if medium._is_remote_before((1, 15)):
2565
return self._vfs_set_parent_location(url)
2567
call_url = url or ''
2568
if type(call_url) is not str:
2569
raise AssertionError('url must be a str or None (%s)' % url)
2570
response = self._call('Branch.set_parent_location',
2571
self._remote_path(), self._lock_token, self._repo_lock_token,
2573
except errors.UnknownSmartMethod:
2574
medium._remember_remote_is_before((1, 15))
2575
return self._vfs_set_parent_location(url)
2577
raise errors.UnexpectedSmartServerResponse(response)
2579
def _vfs_set_parent_location(self, url):
2581
return self._real_branch._set_parent_location(url)
2584
def pull(self, source, overwrite=False, stop_revision=None,
2586
self._clear_cached_state_of_remote_branch_only()
2588
return self._real_branch.pull(
2589
source, overwrite=overwrite, stop_revision=stop_revision,
2590
_override_hook_target=self, **kwargs)
2593
def push(self, target, overwrite=False, stop_revision=None):
2595
return self._real_branch.push(
2596
target, overwrite=overwrite, stop_revision=stop_revision,
2597
_override_hook_source_branch=self)
2599
def is_locked(self):
2600
return self._lock_count >= 1
2603
def revision_id_to_revno(self, revision_id):
2605
return self._real_branch.revision_id_to_revno(revision_id)
2608
def set_last_revision_info(self, revno, revision_id):
2609
# XXX: These should be returned by the set_last_revision_info verb
2610
old_revno, old_revid = self.last_revision_info()
2611
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2612
revision_id = ensure_null(revision_id)
2614
response = self._call('Branch.set_last_revision_info',
2615
self._remote_path(), self._lock_token, self._repo_lock_token,
2616
str(revno), revision_id)
2617
except errors.UnknownSmartMethod:
2619
self._clear_cached_state_of_remote_branch_only()
2620
self._real_branch.set_last_revision_info(revno, revision_id)
2621
self._last_revision_info_cache = revno, revision_id
2623
if response == ('ok',):
2624
self._clear_cached_state()
2625
self._last_revision_info_cache = revno, revision_id
2626
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2627
# Update the _real_branch's cache too.
2628
if self._real_branch is not None:
2629
cache = self._last_revision_info_cache
2630
self._real_branch._last_revision_info_cache = cache
2632
raise errors.UnexpectedSmartServerResponse(response)
2635
def generate_revision_history(self, revision_id, last_rev=None,
2637
medium = self._client._medium
2638
if not medium._is_remote_before((1, 6)):
2639
# Use a smart method for 1.6 and above servers
2641
self._set_last_revision_descendant(revision_id, other_branch,
2642
allow_diverged=True, allow_overwrite_descendant=True)
2644
except errors.UnknownSmartMethod:
2645
medium._remember_remote_is_before((1, 6))
2646
self._clear_cached_state_of_remote_branch_only()
2647
self.set_revision_history(self._lefthand_history(revision_id,
2648
last_rev=last_rev,other_branch=other_branch))
2650
def set_push_location(self, location):
2652
return self._real_branch.set_push_location(location)
2655
class RemoteConfig(object):
2656
"""A Config that reads and writes from smart verbs.
2658
It is a low-level object that considers config data to be name/value pairs
2659
that may be associated with a section. Assigning meaning to the these
2660
values is done at higher levels like bzrlib.config.TreeConfig.
2663
def get_option(self, name, section=None, default=None):
2664
"""Return the value associated with a named option.
2666
:param name: The name of the value
2667
:param section: The section the option is in (if any)
2668
:param default: The value to return if the value is not set
2669
:return: The value or default value
2672
configobj = self._get_configobj()
2674
section_obj = configobj
2677
section_obj = configobj[section]
2680
return section_obj.get(name, default)
2681
except errors.UnknownSmartMethod:
2682
return self._vfs_get_option(name, section, default)
2684
def _response_to_configobj(self, response):
2685
if len(response[0]) and response[0][0] != 'ok':
2686
raise errors.UnexpectedSmartServerResponse(response)
2687
lines = response[1].read_body_bytes().splitlines()
2688
return config.ConfigObj(lines, encoding='utf-8')
2691
class RemoteBranchConfig(RemoteConfig):
2692
"""A RemoteConfig for Branches."""
2694
def __init__(self, branch):
2695
self._branch = branch
2697
def _get_configobj(self):
2698
path = self._branch._remote_path()
2699
response = self._branch._client.call_expecting_body(
2700
'Branch.get_config_file', path)
2701
return self._response_to_configobj(response)
2703
def set_option(self, value, name, section=None):
2704
"""Set the value associated with a named option.
2706
:param value: The value to set
2707
:param name: The name of the value to set
2708
:param section: The section the option is in (if any)
2710
medium = self._branch._client._medium
2711
if medium._is_remote_before((1, 14)):
2712
return self._vfs_set_option(value, name, section)
2714
path = self._branch._remote_path()
2715
response = self._branch._client.call('Branch.set_config_option',
2716
path, self._branch._lock_token, self._branch._repo_lock_token,
2717
value.encode('utf8'), name, section or '')
2718
except errors.UnknownSmartMethod:
2719
medium._remember_remote_is_before((1, 14))
2720
return self._vfs_set_option(value, name, section)
2722
raise errors.UnexpectedSmartServerResponse(response)
2724
def _real_object(self):
2725
self._branch._ensure_real()
2726
return self._branch._real_branch
2728
def _vfs_set_option(self, value, name, section=None):
2729
return self._real_object()._get_config().set_option(
2730
value, name, section)
2733
class RemoteBzrDirConfig(RemoteConfig):
2734
"""A RemoteConfig for BzrDirs."""
2736
def __init__(self, bzrdir):
2737
self._bzrdir = bzrdir
2739
def _get_configobj(self):
2740
medium = self._bzrdir._client._medium
2741
verb = 'BzrDir.get_config_file'
2742
if medium._is_remote_before((1, 15)):
2743
raise errors.UnknownSmartMethod(verb)
2744
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2745
response = self._bzrdir._call_expecting_body(
2747
return self._response_to_configobj(response)
2749
def _vfs_get_option(self, name, section, default):
2750
return self._real_object()._get_config().get_option(
2751
name, section, default)
2753
def set_option(self, value, name, section=None):
2754
"""Set the value associated with a named option.
2756
:param value: The value to set
2757
:param name: The name of the value to set
2758
:param section: The section the option is in (if any)
2760
return self._real_object()._get_config().set_option(
2761
value, name, section)
2763
def _real_object(self):
2764
self._bzrdir._ensure_real()
2765
return self._bzrdir._real_bzrdir
2769
def _extract_tar(tar, to_dir):
2770
"""Extract all the contents of a tarfile object.
2772
A replacement for extractall, which is not present in python2.4
2775
tar.extract(tarinfo, to_dir)
2778
def _translate_error(err, **context):
2779
"""Translate an ErrorFromSmartServer into a more useful error.
2781
Possible context keys:
2789
If the error from the server doesn't match a known pattern, then
2790
UnknownErrorFromSmartServer is raised.
2794
return context[name]
2795
except KeyError, key_err:
2796
mutter('Missing key %r in context %r', key_err.args[0], context)
2799
"""Get the path from the context if present, otherwise use first error
2803
return context['path']
2804
except KeyError, key_err:
2806
return err.error_args[0]
2807
except IndexError, idx_err:
2809
'Missing key %r in context %r', key_err.args[0], context)
2812
if err.error_verb == 'IncompatibleRepositories':
2813
raise errors.IncompatibleRepositories(err.error_args[0],
2814
err.error_args[1], err.error_args[2])
2815
elif err.error_verb == 'NoSuchRevision':
2816
raise NoSuchRevision(find('branch'), err.error_args[0])
2817
elif err.error_verb == 'nosuchrevision':
2818
raise NoSuchRevision(find('repository'), err.error_args[0])
2819
elif err.error_tuple == ('nobranch',):
2820
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2821
elif err.error_verb == 'norepository':
2822
raise errors.NoRepositoryPresent(find('bzrdir'))
2823
elif err.error_verb == 'LockContention':
2824
raise errors.LockContention('(remote lock)')
2825
elif err.error_verb == 'UnlockableTransport':
2826
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2827
elif err.error_verb == 'LockFailed':
2828
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2829
elif err.error_verb == 'TokenMismatch':
2830
raise errors.TokenMismatch(find('token'), '(remote token)')
2831
elif err.error_verb == 'Diverged':
2832
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2833
elif err.error_verb == 'TipChangeRejected':
2834
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2835
elif err.error_verb == 'UnstackableBranchFormat':
2836
raise errors.UnstackableBranchFormat(*err.error_args)
2837
elif err.error_verb == 'UnstackableRepositoryFormat':
2838
raise errors.UnstackableRepositoryFormat(*err.error_args)
2839
elif err.error_verb == 'NotStacked':
2840
raise errors.NotStacked(branch=find('branch'))
2841
elif err.error_verb == 'PermissionDenied':
2843
if len(err.error_args) >= 2:
2844
extra = err.error_args[1]
2847
raise errors.PermissionDenied(path, extra=extra)
2848
elif err.error_verb == 'ReadError':
2850
raise errors.ReadError(path)
2851
elif err.error_verb == 'NoSuchFile':
2853
raise errors.NoSuchFile(path)
2854
elif err.error_verb == 'FileExists':
2855
raise errors.FileExists(err.error_args[0])
2856
elif err.error_verb == 'DirectoryNotEmpty':
2857
raise errors.DirectoryNotEmpty(err.error_args[0])
2858
elif err.error_verb == 'ShortReadvError':
2859
args = err.error_args
2860
raise errors.ShortReadvError(
2861
args[0], int(args[1]), int(args[2]), int(args[3]))
2862
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2863
encoding = str(err.error_args[0]) # encoding must always be a string
2864
val = err.error_args[1]
2865
start = int(err.error_args[2])
2866
end = int(err.error_args[3])
2867
reason = str(err.error_args[4]) # reason must always be a string
2868
if val.startswith('u:'):
2869
val = val[2:].decode('utf-8')
2870
elif val.startswith('s:'):
2871
val = val[2:].decode('base64')
2872
if err.error_verb == 'UnicodeDecodeError':
2873
raise UnicodeDecodeError(encoding, val, start, end, reason)
2874
elif err.error_verb == 'UnicodeEncodeError':
2875
raise UnicodeEncodeError(encoding, val, start, end, reason)
2876
elif err.error_verb == 'ReadOnlyError':
2877
raise errors.TransportNotPossible('readonly transport')
2878
raise errors.UnknownErrorFromSmartServer(err)