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