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
288
('BzrDir.open_branchV3', (2, 1)),
289
('BzrDir.open_branchV2', (1, 13)),
290
('BzrDir.open_branch', None),
292
for verb, required_version in candidate_calls:
293
if required_version and medium._is_remote_before(required_version):
296
response = self._call(verb, path)
297
except errors.UnknownSmartMethod:
298
if required_version is None:
300
medium._remember_remote_is_before(required_version)
303
if verb == 'BzrDir.open_branch':
304
if response[0] != 'ok':
305
raise errors.UnexpectedSmartServerResponse(response)
306
if response[1] != '':
307
return ('ref', response[1])
309
return ('branch', '')
310
if response[0] not in ('ref', 'branch'):
311
raise errors.UnexpectedSmartServerResponse(response)
314
def _get_tree_branch(self):
315
"""See BzrDir._get_tree_branch()."""
316
return None, self.open_branch()
318
def open_branch(self, _unsupported=False, ignore_fallbacks=False):
320
raise NotImplementedError('unsupported flag support not implemented yet.')
321
if self._next_open_branch_result is not None:
322
# See create_branch for details.
323
result = self._next_open_branch_result
324
self._next_open_branch_result = None
326
response = self._get_branch_reference()
327
if response[0] == 'ref':
328
# a branch reference, use the existing BranchReference logic.
329
format = BranchReferenceFormat()
330
return format.open(self, _found=True, location=response[1],
331
ignore_fallbacks=ignore_fallbacks)
332
branch_format_name = response[1]
333
if not branch_format_name:
334
branch_format_name = None
335
format = RemoteBranchFormat(network_name=branch_format_name)
336
return RemoteBranch(self, self.find_repository(), format=format,
337
setup_stacking=not ignore_fallbacks)
339
def _open_repo_v1(self, path):
340
verb = 'BzrDir.find_repository'
341
response = self._call(verb, path)
342
if response[0] != 'ok':
343
raise errors.UnexpectedSmartServerResponse(response)
344
# servers that only support the v1 method don't support external
347
repo = self._real_bzrdir.open_repository()
348
response = response + ('no', repo._format.network_name())
349
return response, repo
351
def _open_repo_v2(self, path):
352
verb = 'BzrDir.find_repositoryV2'
353
response = self._call(verb, path)
354
if response[0] != 'ok':
355
raise errors.UnexpectedSmartServerResponse(response)
357
repo = self._real_bzrdir.open_repository()
358
response = response + (repo._format.network_name(),)
359
return response, repo
361
def _open_repo_v3(self, path):
362
verb = 'BzrDir.find_repositoryV3'
363
medium = self._client._medium
364
if medium._is_remote_before((1, 13)):
365
raise errors.UnknownSmartMethod(verb)
367
response = self._call(verb, path)
368
except errors.UnknownSmartMethod:
369
medium._remember_remote_is_before((1, 13))
371
if response[0] != 'ok':
372
raise errors.UnexpectedSmartServerResponse(response)
373
return response, None
375
def open_repository(self):
376
path = self._path_for_remote_call(self._client)
378
for probe in [self._open_repo_v3, self._open_repo_v2,
381
response, real_repo = probe(path)
383
except errors.UnknownSmartMethod:
386
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
387
if response[0] != 'ok':
388
raise errors.UnexpectedSmartServerResponse(response)
389
if len(response) != 6:
390
raise SmartProtocolError('incorrect response length %s' % (response,))
391
if response[1] == '':
392
# repo is at this dir.
393
format = response_tuple_to_repo_format(response[2:])
394
# Used to support creating a real format instance when needed.
395
format._creating_bzrdir = self
396
remote_repo = RemoteRepository(self, format)
397
format._creating_repo = remote_repo
398
if real_repo is not None:
399
remote_repo._set_real_repository(real_repo)
402
raise errors.NoRepositoryPresent(self)
404
def has_workingtree(self):
405
if self._has_working_tree is None:
407
self._has_working_tree = self._real_bzrdir.has_workingtree()
408
return self._has_working_tree
410
def open_workingtree(self, recommend_upgrade=True):
411
if self.has_workingtree():
412
raise errors.NotLocalUrl(self.root_transport)
414
raise errors.NoWorkingTree(self.root_transport.base)
416
def _path_for_remote_call(self, client):
417
"""Return the path to be used for this bzrdir in a remote call."""
418
return client.remote_path_from_transport(self.root_transport)
420
def get_branch_transport(self, branch_format):
422
return self._real_bzrdir.get_branch_transport(branch_format)
424
def get_repository_transport(self, repository_format):
426
return self._real_bzrdir.get_repository_transport(repository_format)
428
def get_workingtree_transport(self, workingtree_format):
430
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
432
def can_convert_format(self):
433
"""Upgrading of remote bzrdirs is not supported yet."""
436
def needs_format_conversion(self, format=None):
437
"""Upgrading of remote bzrdirs is not supported yet."""
439
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
440
% 'needs_format_conversion(format=None)')
443
def clone(self, url, revision_id=None, force_new_repo=False,
444
preserve_stacking=False):
446
return self._real_bzrdir.clone(url, revision_id=revision_id,
447
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
449
def _get_config(self):
450
return RemoteBzrDirConfig(self)
453
class RemoteRepositoryFormat(repository.RepositoryFormat):
454
"""Format for repositories accessed over a _SmartClient.
456
Instances of this repository are represented by RemoteRepository
459
The RemoteRepositoryFormat is parameterized during construction
460
to reflect the capabilities of the real, remote format. Specifically
461
the attributes rich_root_data and supports_tree_reference are set
462
on a per instance basis, and are not set (and should not be) at
465
:ivar _custom_format: If set, a specific concrete repository format that
466
will be used when initializing a repository with this
467
RemoteRepositoryFormat.
468
:ivar _creating_repo: If set, the repository object that this
469
RemoteRepositoryFormat was created for: it can be called into
470
to obtain data like the network name.
473
_matchingbzrdir = RemoteBzrDirFormat()
476
repository.RepositoryFormat.__init__(self)
477
self._custom_format = None
478
self._network_name = None
479
self._creating_bzrdir = None
480
self._supports_chks = None
481
self._supports_external_lookups = None
482
self._supports_tree_reference = None
483
self._rich_root_data = None
486
return "%s(_network_name=%r)" % (self.__class__.__name__,
490
def fast_deltas(self):
492
return self._custom_format.fast_deltas
495
def rich_root_data(self):
496
if self._rich_root_data is None:
498
self._rich_root_data = self._custom_format.rich_root_data
499
return self._rich_root_data
502
def supports_chks(self):
503
if self._supports_chks is None:
505
self._supports_chks = self._custom_format.supports_chks
506
return self._supports_chks
509
def supports_external_lookups(self):
510
if self._supports_external_lookups is None:
512
self._supports_external_lookups = \
513
self._custom_format.supports_external_lookups
514
return self._supports_external_lookups
517
def supports_tree_reference(self):
518
if self._supports_tree_reference is None:
520
self._supports_tree_reference = \
521
self._custom_format.supports_tree_reference
522
return self._supports_tree_reference
524
def _vfs_initialize(self, a_bzrdir, shared):
525
"""Helper for common code in initialize."""
526
if self._custom_format:
527
# Custom format requested
528
result = self._custom_format.initialize(a_bzrdir, shared=shared)
529
elif self._creating_bzrdir is not None:
530
# Use the format that the repository we were created to back
532
prior_repo = self._creating_bzrdir.open_repository()
533
prior_repo._ensure_real()
534
result = prior_repo._real_repository._format.initialize(
535
a_bzrdir, shared=shared)
537
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
538
# support remote initialization.
539
# We delegate to a real object at this point (as RemoteBzrDir
540
# delegate to the repository format which would lead to infinite
541
# recursion if we just called a_bzrdir.create_repository.
542
a_bzrdir._ensure_real()
543
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
544
if not isinstance(result, RemoteRepository):
545
return self.open(a_bzrdir)
549
def initialize(self, a_bzrdir, shared=False):
550
# Being asked to create on a non RemoteBzrDir:
551
if not isinstance(a_bzrdir, RemoteBzrDir):
552
return self._vfs_initialize(a_bzrdir, shared)
553
medium = a_bzrdir._client._medium
554
if medium._is_remote_before((1, 13)):
555
return self._vfs_initialize(a_bzrdir, shared)
556
# Creating on a remote bzr dir.
557
# 1) get the network name to use.
558
if self._custom_format:
559
network_name = self._custom_format.network_name()
560
elif self._network_name:
561
network_name = self._network_name
563
# Select the current bzrlib default and ask for that.
564
reference_bzrdir_format = bzrdir.format_registry.get('default')()
565
reference_format = reference_bzrdir_format.repository_format
566
network_name = reference_format.network_name()
567
# 2) try direct creation via RPC
568
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
569
verb = 'BzrDir.create_repository'
575
response = a_bzrdir._call(verb, path, network_name, shared_str)
576
except errors.UnknownSmartMethod:
577
# Fallback - use vfs methods
578
medium._remember_remote_is_before((1, 13))
579
return self._vfs_initialize(a_bzrdir, shared)
581
# Turn the response into a RemoteRepository object.
582
format = response_tuple_to_repo_format(response[1:])
583
# Used to support creating a real format instance when needed.
584
format._creating_bzrdir = a_bzrdir
585
remote_repo = RemoteRepository(a_bzrdir, format)
586
format._creating_repo = remote_repo
589
def open(self, a_bzrdir):
590
if not isinstance(a_bzrdir, RemoteBzrDir):
591
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
592
return a_bzrdir.open_repository()
594
def _ensure_real(self):
595
if self._custom_format is None:
596
self._custom_format = repository.network_format_registry.get(
600
def _fetch_order(self):
602
return self._custom_format._fetch_order
605
def _fetch_uses_deltas(self):
607
return self._custom_format._fetch_uses_deltas
610
def _fetch_reconcile(self):
612
return self._custom_format._fetch_reconcile
614
def get_format_description(self):
616
return 'Remote: ' + self._custom_format.get_format_description()
618
def __eq__(self, other):
619
return self.__class__ is other.__class__
621
def network_name(self):
622
if self._network_name:
623
return self._network_name
624
self._creating_repo._ensure_real()
625
return self._creating_repo._real_repository._format.network_name()
628
def pack_compresses(self):
630
return self._custom_format.pack_compresses
633
def _serializer(self):
635
return self._custom_format._serializer
638
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
639
"""Repository accessed over rpc.
641
For the moment most operations are performed using local transport-backed
645
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
646
"""Create a RemoteRepository instance.
648
:param remote_bzrdir: The bzrdir hosting this repository.
649
:param format: The RemoteFormat object to use.
650
:param real_repository: If not None, a local implementation of the
651
repository logic for the repository, usually accessing the data
653
:param _client: Private testing parameter - override the smart client
654
to be used by the repository.
657
self._real_repository = real_repository
659
self._real_repository = None
660
self.bzrdir = remote_bzrdir
662
self._client = remote_bzrdir._client
664
self._client = _client
665
self._format = format
666
self._lock_mode = None
667
self._lock_token = None
669
self._leave_lock = False
670
# Cache of revision parents; misses are cached during read locks, and
671
# write locks when no _real_repository has been set.
672
self._unstacked_provider = graph.CachingParentsProvider(
673
get_parent_map=self._get_parent_map_rpc)
674
self._unstacked_provider.disable_cache()
676
# These depend on the actual remote format, so force them off for
677
# maximum compatibility. XXX: In future these should depend on the
678
# remote repository instance, but this is irrelevant until we perform
679
# reconcile via an RPC call.
680
self._reconcile_does_inventory_gc = False
681
self._reconcile_fixes_text_parents = False
682
self._reconcile_backsup_inventory = False
683
self.base = self.bzrdir.transport.base
684
# Additional places to query for data.
685
self._fallback_repositories = []
688
return "%s(%s)" % (self.__class__.__name__, self.base)
692
def abort_write_group(self, suppress_errors=False):
693
"""Complete a write group on the decorated repository.
695
Smart methods perform operations in a single step so this API
696
is not really applicable except as a compatibility thunk
697
for older plugins that don't use e.g. the CommitBuilder
700
:param suppress_errors: see Repository.abort_write_group.
703
return self._real_repository.abort_write_group(
704
suppress_errors=suppress_errors)
708
"""Decorate the real repository for now.
710
In the long term a full blown network facility is needed to avoid
711
creating a real repository object locally.
714
return self._real_repository.chk_bytes
716
def commit_write_group(self):
717
"""Complete a write group on the decorated repository.
719
Smart methods perform operations in a single step so this API
720
is not really applicable except as a compatibility thunk
721
for older plugins that don't use e.g. the CommitBuilder
725
return self._real_repository.commit_write_group()
727
def resume_write_group(self, tokens):
729
return self._real_repository.resume_write_group(tokens)
731
def suspend_write_group(self):
733
return self._real_repository.suspend_write_group()
735
def get_missing_parent_inventories(self, check_for_missing_texts=True):
737
return self._real_repository.get_missing_parent_inventories(
738
check_for_missing_texts=check_for_missing_texts)
740
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
742
return self._real_repository.get_rev_id_for_revno(
745
def get_rev_id_for_revno(self, revno, known_pair):
746
"""See Repository.get_rev_id_for_revno."""
747
path = self.bzrdir._path_for_remote_call(self._client)
749
if self._client._medium._is_remote_before((1, 17)):
750
return self._get_rev_id_for_revno_vfs(revno, known_pair)
751
response = self._call(
752
'Repository.get_rev_id_for_revno', path, revno, known_pair)
753
except errors.UnknownSmartMethod:
754
self._client._medium._remember_remote_is_before((1, 17))
755
return self._get_rev_id_for_revno_vfs(revno, known_pair)
756
if response[0] == 'ok':
757
return True, response[1]
758
elif response[0] == 'history-incomplete':
759
known_pair = response[1:3]
760
for fallback in self._fallback_repositories:
761
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
766
# Not found in any fallbacks
767
return False, known_pair
769
raise errors.UnexpectedSmartServerResponse(response)
771
def _ensure_real(self):
772
"""Ensure that there is a _real_repository set.
774
Used before calls to self._real_repository.
776
Note that _ensure_real causes many roundtrips to the server which are
777
not desirable, and prevents the use of smart one-roundtrip RPC's to
778
perform complex operations (such as accessing parent data, streaming
779
revisions etc). Adding calls to _ensure_real should only be done when
780
bringing up new functionality, adding fallbacks for smart methods that
781
require a fallback path, and never to replace an existing smart method
782
invocation. If in doubt chat to the bzr network team.
784
if self._real_repository is None:
785
if 'hpssvfs' in debug.debug_flags:
787
warning('VFS Repository access triggered\n%s',
788
''.join(traceback.format_stack()))
789
self._unstacked_provider.missing_keys.clear()
790
self.bzrdir._ensure_real()
791
self._set_real_repository(
792
self.bzrdir._real_bzrdir.open_repository())
794
def _translate_error(self, err, **context):
795
self.bzrdir._translate_error(err, repository=self, **context)
797
def find_text_key_references(self):
798
"""Find the text key references within the repository.
800
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
801
revision_ids. Each altered file-ids has the exact revision_ids that
802
altered it listed explicitly.
803
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
804
to whether they were referred to by the inventory of the
805
revision_id that they contain. The inventory texts from all present
806
revision ids are assessed to generate this report.
809
return self._real_repository.find_text_key_references()
811
def _generate_text_key_index(self):
812
"""Generate a new text key index for the repository.
814
This is an expensive function that will take considerable time to run.
816
:return: A dict mapping (file_id, revision_id) tuples to a list of
817
parents, also (file_id, revision_id) tuples.
820
return self._real_repository._generate_text_key_index()
822
def _get_revision_graph(self, revision_id):
823
"""Private method for using with old (< 1.2) servers to fallback."""
824
if revision_id is None:
826
elif revision.is_null(revision_id):
829
path = self.bzrdir._path_for_remote_call(self._client)
830
response = self._call_expecting_body(
831
'Repository.get_revision_graph', path, revision_id)
832
response_tuple, response_handler = response
833
if response_tuple[0] != 'ok':
834
raise errors.UnexpectedSmartServerResponse(response_tuple)
835
coded = response_handler.read_body_bytes()
837
# no revisions in this repository!
839
lines = coded.split('\n')
842
d = tuple(line.split())
843
revision_graph[d[0]] = d[1:]
845
return revision_graph
848
"""See Repository._get_sink()."""
849
return RemoteStreamSink(self)
851
def _get_source(self, to_format):
852
"""Return a source for streaming from this repository."""
853
return RemoteStreamSource(self, to_format)
856
def has_revision(self, revision_id):
857
"""True if this repository has a copy of the revision."""
858
# Copy of bzrlib.repository.Repository.has_revision
859
return revision_id in self.has_revisions((revision_id,))
862
def has_revisions(self, revision_ids):
863
"""Probe to find out the presence of multiple revisions.
865
:param revision_ids: An iterable of revision_ids.
866
:return: A set of the revision_ids that were present.
868
# Copy of bzrlib.repository.Repository.has_revisions
869
parent_map = self.get_parent_map(revision_ids)
870
result = set(parent_map)
871
if _mod_revision.NULL_REVISION in revision_ids:
872
result.add(_mod_revision.NULL_REVISION)
875
def _has_same_fallbacks(self, other_repo):
876
"""Returns true if the repositories have the same fallbacks."""
877
# XXX: copied from Repository; it should be unified into a base class
878
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
879
my_fb = self._fallback_repositories
880
other_fb = other_repo._fallback_repositories
881
if len(my_fb) != len(other_fb):
883
for f, g in zip(my_fb, other_fb):
884
if not f.has_same_location(g):
888
def has_same_location(self, other):
889
# TODO: Move to RepositoryBase and unify with the regular Repository
890
# one; unfortunately the tests rely on slightly different behaviour at
891
# present -- mbp 20090710
892
return (self.__class__ is other.__class__ and
893
self.bzrdir.transport.base == other.bzrdir.transport.base)
895
def get_graph(self, other_repository=None):
896
"""Return the graph for this repository format"""
897
parents_provider = self._make_parents_provider(other_repository)
898
return graph.Graph(parents_provider)
900
def gather_stats(self, revid=None, committers=None):
901
"""See Repository.gather_stats()."""
902
path = self.bzrdir._path_for_remote_call(self._client)
903
# revid can be None to indicate no revisions, not just NULL_REVISION
904
if revid is None or revision.is_null(revid):
908
if committers is None or not committers:
909
fmt_committers = 'no'
911
fmt_committers = 'yes'
912
response_tuple, response_handler = self._call_expecting_body(
913
'Repository.gather_stats', path, fmt_revid, fmt_committers)
914
if response_tuple[0] != 'ok':
915
raise errors.UnexpectedSmartServerResponse(response_tuple)
917
body = response_handler.read_body_bytes()
919
for line in body.split('\n'):
922
key, val_text = line.split(':')
923
if key in ('revisions', 'size', 'committers'):
924
result[key] = int(val_text)
925
elif key in ('firstrev', 'latestrev'):
926
values = val_text.split(' ')[1:]
927
result[key] = (float(values[0]), long(values[1]))
931
def find_branches(self, using=False):
932
"""See Repository.find_branches()."""
933
# should be an API call to the server.
935
return self._real_repository.find_branches(using=using)
937
def get_physical_lock_status(self):
938
"""See Repository.get_physical_lock_status()."""
939
# should be an API call to the server.
941
return self._real_repository.get_physical_lock_status()
943
def is_in_write_group(self):
944
"""Return True if there is an open write group.
946
write groups are only applicable locally for the smart server..
948
if self._real_repository:
949
return self._real_repository.is_in_write_group()
952
return self._lock_count >= 1
955
"""See Repository.is_shared()."""
956
path = self.bzrdir._path_for_remote_call(self._client)
957
response = self._call('Repository.is_shared', path)
958
if response[0] not in ('yes', 'no'):
959
raise SmartProtocolError('unexpected response code %s' % (response,))
960
return response[0] == 'yes'
962
def is_write_locked(self):
963
return self._lock_mode == 'w'
965
def _warn_if_deprecated(self, branch=None):
966
# If we have a real repository, the check will be done there, if we
967
# don't the check will be done remotely.
971
# wrong eventually - want a local lock cache context
972
if not self._lock_mode:
974
self._lock_mode = 'r'
976
self._unstacked_provider.enable_cache(cache_misses=True)
977
if self._real_repository is not None:
978
self._real_repository.lock_read()
979
for repo in self._fallback_repositories:
982
self._lock_count += 1
984
def _remote_lock_write(self, token):
985
path = self.bzrdir._path_for_remote_call(self._client)
988
err_context = {'token': token}
989
response = self._call('Repository.lock_write', path, token,
991
if response[0] == 'ok':
995
raise errors.UnexpectedSmartServerResponse(response)
997
def lock_write(self, token=None, _skip_rpc=False):
998
if not self._lock_mode:
1001
if self._lock_token is not None:
1002
if token != self._lock_token:
1003
raise errors.TokenMismatch(token, self._lock_token)
1004
self._lock_token = token
1006
self._lock_token = self._remote_lock_write(token)
1007
# if self._lock_token is None, then this is something like packs or
1008
# svn where we don't get to lock the repo, or a weave style repository
1009
# where we cannot lock it over the wire and attempts to do so will
1011
if self._real_repository is not None:
1012
self._real_repository.lock_write(token=self._lock_token)
1013
if token is not None:
1014
self._leave_lock = True
1016
self._leave_lock = False
1017
self._lock_mode = 'w'
1018
self._lock_count = 1
1019
cache_misses = self._real_repository is None
1020
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1021
for repo in self._fallback_repositories:
1022
# Writes don't affect fallback repos
1024
elif self._lock_mode == 'r':
1025
raise errors.ReadOnlyError(self)
1027
self._lock_count += 1
1028
return self._lock_token or None
1030
def leave_lock_in_place(self):
1031
if not self._lock_token:
1032
raise NotImplementedError(self.leave_lock_in_place)
1033
self._leave_lock = True
1035
def dont_leave_lock_in_place(self):
1036
if not self._lock_token:
1037
raise NotImplementedError(self.dont_leave_lock_in_place)
1038
self._leave_lock = False
1040
def _set_real_repository(self, repository):
1041
"""Set the _real_repository for this repository.
1043
:param repository: The repository to fallback to for non-hpss
1044
implemented operations.
1046
if self._real_repository is not None:
1047
# Replacing an already set real repository.
1048
# We cannot do this [currently] if the repository is locked -
1049
# synchronised state might be lost.
1050
if self.is_locked():
1051
raise AssertionError('_real_repository is already set')
1052
if isinstance(repository, RemoteRepository):
1053
raise AssertionError()
1054
self._real_repository = repository
1055
# three code paths happen here:
1056
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1057
# up stacking. In this case self._fallback_repositories is [], and the
1058
# real repo is already setup. Preserve the real repo and
1059
# RemoteRepository.add_fallback_repository will avoid adding
1061
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1062
# ensure_real is triggered from a branch, the real repository to
1063
# set already has a matching list with separate instances, but
1064
# as they are also RemoteRepositories we don't worry about making the
1065
# lists be identical.
1066
# 3) new servers, RemoteRepository.ensure_real is triggered before
1067
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1068
# and need to populate it.
1069
if (self._fallback_repositories and
1070
len(self._real_repository._fallback_repositories) !=
1071
len(self._fallback_repositories)):
1072
if len(self._real_repository._fallback_repositories):
1073
raise AssertionError(
1074
"cannot cleanly remove existing _fallback_repositories")
1075
for fb in self._fallback_repositories:
1076
self._real_repository.add_fallback_repository(fb)
1077
if self._lock_mode == 'w':
1078
# if we are already locked, the real repository must be able to
1079
# acquire the lock with our token.
1080
self._real_repository.lock_write(self._lock_token)
1081
elif self._lock_mode == 'r':
1082
self._real_repository.lock_read()
1084
def start_write_group(self):
1085
"""Start a write group on the decorated repository.
1087
Smart methods perform operations in a single step so this API
1088
is not really applicable except as a compatibility thunk
1089
for older plugins that don't use e.g. the CommitBuilder
1093
return self._real_repository.start_write_group()
1095
def _unlock(self, token):
1096
path = self.bzrdir._path_for_remote_call(self._client)
1098
# with no token the remote repository is not persistently locked.
1100
err_context = {'token': token}
1101
response = self._call('Repository.unlock', path, token,
1103
if response == ('ok',):
1106
raise errors.UnexpectedSmartServerResponse(response)
1108
@only_raises(errors.LockNotHeld, errors.LockBroken)
1110
if not self._lock_count:
1111
return lock.cant_unlock_not_held(self)
1112
self._lock_count -= 1
1113
if self._lock_count > 0:
1115
self._unstacked_provider.disable_cache()
1116
old_mode = self._lock_mode
1117
self._lock_mode = None
1119
# The real repository is responsible at present for raising an
1120
# exception if it's in an unfinished write group. However, it
1121
# normally will *not* actually remove the lock from disk - that's
1122
# done by the server on receiving the Repository.unlock call.
1123
# This is just to let the _real_repository stay up to date.
1124
if self._real_repository is not None:
1125
self._real_repository.unlock()
1127
# The rpc-level lock should be released even if there was a
1128
# problem releasing the vfs-based lock.
1130
# Only write-locked repositories need to make a remote method
1131
# call to perform the unlock.
1132
old_token = self._lock_token
1133
self._lock_token = None
1134
if not self._leave_lock:
1135
self._unlock(old_token)
1136
# Fallbacks are always 'lock_read()' so we don't pay attention to
1138
for repo in self._fallback_repositories:
1141
def break_lock(self):
1142
# should hand off to the network
1144
return self._real_repository.break_lock()
1146
def _get_tarball(self, compression):
1147
"""Return a TemporaryFile containing a repository tarball.
1149
Returns None if the server does not support sending tarballs.
1152
path = self.bzrdir._path_for_remote_call(self._client)
1154
response, protocol = self._call_expecting_body(
1155
'Repository.tarball', path, compression)
1156
except errors.UnknownSmartMethod:
1157
protocol.cancel_read_body()
1159
if response[0] == 'ok':
1160
# Extract the tarball and return it
1161
t = tempfile.NamedTemporaryFile()
1162
# TODO: rpc layer should read directly into it...
1163
t.write(protocol.read_body_bytes())
1166
raise errors.UnexpectedSmartServerResponse(response)
1168
def sprout(self, to_bzrdir, revision_id=None):
1169
# TODO: Option to control what format is created?
1171
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1173
dest_repo.fetch(self, revision_id=revision_id)
1176
### These methods are just thin shims to the VFS object for now.
1178
def revision_tree(self, revision_id):
1180
return self._real_repository.revision_tree(revision_id)
1182
def get_serializer_format(self):
1184
return self._real_repository.get_serializer_format()
1186
def get_commit_builder(self, branch, parents, config, timestamp=None,
1187
timezone=None, committer=None, revprops=None,
1189
# FIXME: It ought to be possible to call this without immediately
1190
# triggering _ensure_real. For now it's the easiest thing to do.
1192
real_repo = self._real_repository
1193
builder = real_repo.get_commit_builder(branch, parents,
1194
config, timestamp=timestamp, timezone=timezone,
1195
committer=committer, revprops=revprops, revision_id=revision_id)
1198
def add_fallback_repository(self, repository):
1199
"""Add a repository to use for looking up data not held locally.
1201
:param repository: A repository.
1203
if not self._format.supports_external_lookups:
1204
raise errors.UnstackableRepositoryFormat(
1205
self._format.network_name(), self.base)
1206
# We need to accumulate additional repositories here, to pass them in
1209
if self.is_locked():
1210
# We will call fallback.unlock() when we transition to the unlocked
1211
# state, so always add a lock here. If a caller passes us a locked
1212
# repository, they are responsible for unlocking it later.
1213
repository.lock_read()
1214
self._fallback_repositories.append(repository)
1215
# If self._real_repository was parameterised already (e.g. because a
1216
# _real_branch had its get_stacked_on_url method called), then the
1217
# repository to be added may already be in the _real_repositories list.
1218
if self._real_repository is not None:
1219
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1220
self._real_repository._fallback_repositories]
1221
if repository.bzrdir.root_transport.base not in fallback_locations:
1222
self._real_repository.add_fallback_repository(repository)
1224
def add_inventory(self, revid, inv, parents):
1226
return self._real_repository.add_inventory(revid, inv, parents)
1228
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1231
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1232
delta, new_revision_id, parents)
1234
def add_revision(self, rev_id, rev, inv=None, config=None):
1236
return self._real_repository.add_revision(
1237
rev_id, rev, inv=inv, config=config)
1240
def get_inventory(self, revision_id):
1242
return self._real_repository.get_inventory(revision_id)
1244
def iter_inventories(self, revision_ids, ordering=None):
1246
return self._real_repository.iter_inventories(revision_ids, ordering)
1249
def get_revision(self, revision_id):
1251
return self._real_repository.get_revision(revision_id)
1253
def get_transaction(self):
1255
return self._real_repository.get_transaction()
1258
def clone(self, a_bzrdir, revision_id=None):
1260
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1262
def make_working_trees(self):
1263
"""See Repository.make_working_trees"""
1265
return self._real_repository.make_working_trees()
1267
def refresh_data(self):
1268
"""Re-read any data needed to to synchronise with disk.
1270
This method is intended to be called after another repository instance
1271
(such as one used by a smart server) has inserted data into the
1272
repository. It may not be called during a write group, but may be
1273
called at any other time.
1275
if self.is_in_write_group():
1276
raise errors.InternalBzrError(
1277
"May not refresh_data while in a write group.")
1278
if self._real_repository is not None:
1279
self._real_repository.refresh_data()
1281
def revision_ids_to_search_result(self, result_set):
1282
"""Convert a set of revision ids to a graph SearchResult."""
1283
result_parents = set()
1284
for parents in self.get_graph().get_parent_map(
1285
result_set).itervalues():
1286
result_parents.update(parents)
1287
included_keys = result_set.intersection(result_parents)
1288
start_keys = result_set.difference(included_keys)
1289
exclude_keys = result_parents.difference(result_set)
1290
result = graph.SearchResult(start_keys, exclude_keys,
1291
len(result_set), result_set)
1295
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1296
"""Return the revision ids that other has that this does not.
1298
These are returned in topological order.
1300
revision_id: only return revision ids included by revision_id.
1302
return repository.InterRepository.get(
1303
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1305
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1307
# No base implementation to use as RemoteRepository is not a subclass
1308
# of Repository; so this is a copy of Repository.fetch().
1309
if fetch_spec is not None and revision_id is not None:
1310
raise AssertionError(
1311
"fetch_spec and revision_id are mutually exclusive.")
1312
if self.is_in_write_group():
1313
raise errors.InternalBzrError(
1314
"May not fetch while in a write group.")
1315
# fast path same-url fetch operations
1316
if (self.has_same_location(source)
1317
and fetch_spec is None
1318
and self._has_same_fallbacks(source)):
1319
# check that last_revision is in 'from' and then return a
1321
if (revision_id is not None and
1322
not revision.is_null(revision_id)):
1323
self.get_revision(revision_id)
1325
# if there is no specific appropriate InterRepository, this will get
1326
# the InterRepository base class, which raises an
1327
# IncompatibleRepositories when asked to fetch.
1328
inter = repository.InterRepository.get(source, self)
1329
return inter.fetch(revision_id=revision_id, pb=pb,
1330
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1332
def create_bundle(self, target, base, fileobj, format=None):
1334
self._real_repository.create_bundle(target, base, fileobj, format)
1337
def get_ancestry(self, revision_id, topo_sorted=True):
1339
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1341
def fileids_altered_by_revision_ids(self, revision_ids):
1343
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1345
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1347
return self._real_repository._get_versioned_file_checker(
1348
revisions, revision_versions_cache)
1350
def iter_files_bytes(self, desired_files):
1351
"""See Repository.iter_file_bytes.
1354
return self._real_repository.iter_files_bytes(desired_files)
1356
def get_parent_map(self, revision_ids):
1357
"""See bzrlib.Graph.get_parent_map()."""
1358
return self._make_parents_provider().get_parent_map(revision_ids)
1360
def _get_parent_map_rpc(self, keys):
1361
"""Helper for get_parent_map that performs the RPC."""
1362
medium = self._client._medium
1363
if medium._is_remote_before((1, 2)):
1364
# We already found out that the server can't understand
1365
# Repository.get_parent_map requests, so just fetch the whole
1368
# Note that this reads the whole graph, when only some keys are
1369
# wanted. On this old server there's no way (?) to get them all
1370
# in one go, and the user probably will have seen a warning about
1371
# the server being old anyhow.
1372
rg = self._get_revision_graph(None)
1373
# There is an API discrepancy between get_parent_map and
1374
# get_revision_graph. Specifically, a "key:()" pair in
1375
# get_revision_graph just means a node has no parents. For
1376
# "get_parent_map" it means the node is a ghost. So fix up the
1377
# graph to correct this.
1378
# https://bugs.launchpad.net/bzr/+bug/214894
1379
# There is one other "bug" which is that ghosts in
1380
# get_revision_graph() are not returned at all. But we won't worry
1381
# about that for now.
1382
for node_id, parent_ids in rg.iteritems():
1383
if parent_ids == ():
1384
rg[node_id] = (NULL_REVISION,)
1385
rg[NULL_REVISION] = ()
1390
raise ValueError('get_parent_map(None) is not valid')
1391
if NULL_REVISION in keys:
1392
keys.discard(NULL_REVISION)
1393
found_parents = {NULL_REVISION:()}
1395
return found_parents
1398
# TODO(Needs analysis): We could assume that the keys being requested
1399
# from get_parent_map are in a breadth first search, so typically they
1400
# will all be depth N from some common parent, and we don't have to
1401
# have the server iterate from the root parent, but rather from the
1402
# keys we're searching; and just tell the server the keyspace we
1403
# already have; but this may be more traffic again.
1405
# Transform self._parents_map into a search request recipe.
1406
# TODO: Manage this incrementally to avoid covering the same path
1407
# repeatedly. (The server will have to on each request, but the less
1408
# work done the better).
1410
# Negative caching notes:
1411
# new server sends missing when a request including the revid
1412
# 'include-missing:' is present in the request.
1413
# missing keys are serialised as missing:X, and we then call
1414
# provider.note_missing(X) for-all X
1415
parents_map = self._unstacked_provider.get_cached_map()
1416
if parents_map is None:
1417
# Repository is not locked, so there's no cache.
1419
# start_set is all the keys in the cache
1420
start_set = set(parents_map)
1421
# result set is all the references to keys in the cache
1422
result_parents = set()
1423
for parents in parents_map.itervalues():
1424
result_parents.update(parents)
1425
stop_keys = result_parents.difference(start_set)
1426
# We don't need to send ghosts back to the server as a position to
1428
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1429
key_count = len(parents_map)
1430
if (NULL_REVISION in result_parents
1431
and NULL_REVISION in self._unstacked_provider.missing_keys):
1432
# If we pruned NULL_REVISION from the stop_keys because it's also
1433
# in our cache of "missing" keys we need to increment our key count
1434
# by 1, because the reconsitituted SearchResult on the server will
1435
# still consider NULL_REVISION to be an included key.
1437
included_keys = start_set.intersection(result_parents)
1438
start_set.difference_update(included_keys)
1439
recipe = ('manual', start_set, stop_keys, key_count)
1440
body = self._serialise_search_recipe(recipe)
1441
path = self.bzrdir._path_for_remote_call(self._client)
1443
if type(key) is not str:
1445
"key %r not a plain string" % (key,))
1446
verb = 'Repository.get_parent_map'
1447
args = (path, 'include-missing:') + tuple(keys)
1449
response = self._call_with_body_bytes_expecting_body(
1451
except errors.UnknownSmartMethod:
1452
# Server does not support this method, so get the whole graph.
1453
# Worse, we have to force a disconnection, because the server now
1454
# doesn't realise it has a body on the wire to consume, so the
1455
# only way to recover is to abandon the connection.
1457
'Server is too old for fast get_parent_map, reconnecting. '
1458
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1460
# To avoid having to disconnect repeatedly, we keep track of the
1461
# fact the server doesn't understand remote methods added in 1.2.
1462
medium._remember_remote_is_before((1, 2))
1463
# Recurse just once and we should use the fallback code.
1464
return self._get_parent_map_rpc(keys)
1465
response_tuple, response_handler = response
1466
if response_tuple[0] not in ['ok']:
1467
response_handler.cancel_read_body()
1468
raise errors.UnexpectedSmartServerResponse(response_tuple)
1469
if response_tuple[0] == 'ok':
1470
coded = bz2.decompress(response_handler.read_body_bytes())
1472
# no revisions found
1474
lines = coded.split('\n')
1477
d = tuple(line.split())
1479
revision_graph[d[0]] = d[1:]
1482
if d[0].startswith('missing:'):
1484
self._unstacked_provider.note_missing_key(revid)
1486
# no parents - so give the Graph result
1488
revision_graph[d[0]] = (NULL_REVISION,)
1489
return revision_graph
1492
def get_signature_text(self, revision_id):
1494
return self._real_repository.get_signature_text(revision_id)
1497
def get_inventory_xml(self, revision_id):
1499
return self._real_repository.get_inventory_xml(revision_id)
1501
def deserialise_inventory(self, revision_id, xml):
1503
return self._real_repository.deserialise_inventory(revision_id, xml)
1505
def reconcile(self, other=None, thorough=False):
1507
return self._real_repository.reconcile(other=other, thorough=thorough)
1509
def all_revision_ids(self):
1511
return self._real_repository.all_revision_ids()
1514
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1516
return self._real_repository.get_deltas_for_revisions(revisions,
1517
specific_fileids=specific_fileids)
1520
def get_revision_delta(self, revision_id, specific_fileids=None):
1522
return self._real_repository.get_revision_delta(revision_id,
1523
specific_fileids=specific_fileids)
1526
def revision_trees(self, revision_ids):
1528
return self._real_repository.revision_trees(revision_ids)
1531
def get_revision_reconcile(self, revision_id):
1533
return self._real_repository.get_revision_reconcile(revision_id)
1536
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1538
return self._real_repository.check(revision_ids=revision_ids,
1539
callback_refs=callback_refs, check_repo=check_repo)
1541
def copy_content_into(self, destination, revision_id=None):
1543
return self._real_repository.copy_content_into(
1544
destination, revision_id=revision_id)
1546
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1547
# get a tarball of the remote repository, and copy from that into the
1549
from bzrlib import osutils
1551
# TODO: Maybe a progress bar while streaming the tarball?
1552
note("Copying repository content as tarball...")
1553
tar_file = self._get_tarball('bz2')
1554
if tar_file is None:
1556
destination = to_bzrdir.create_repository()
1558
tar = tarfile.open('repository', fileobj=tar_file,
1560
tmpdir = osutils.mkdtemp()
1562
_extract_tar(tar, tmpdir)
1563
tmp_bzrdir = BzrDir.open(tmpdir)
1564
tmp_repo = tmp_bzrdir.open_repository()
1565
tmp_repo.copy_content_into(destination, revision_id)
1567
osutils.rmtree(tmpdir)
1571
# TODO: Suggestion from john: using external tar is much faster than
1572
# python's tarfile library, but it may not work on windows.
1575
def inventories(self):
1576
"""Decorate the real repository for now.
1578
In the long term a full blown network facility is needed to
1579
avoid creating a real repository object locally.
1582
return self._real_repository.inventories
1585
def pack(self, hint=None):
1586
"""Compress the data within the repository.
1588
This is not currently implemented within the smart server.
1591
return self._real_repository.pack(hint=hint)
1594
def revisions(self):
1595
"""Decorate the real repository for now.
1597
In the short term this should become a real object to intercept graph
1600
In the long term a full blown network facility is needed.
1603
return self._real_repository.revisions
1605
def set_make_working_trees(self, new_value):
1607
new_value_str = "True"
1609
new_value_str = "False"
1610
path = self.bzrdir._path_for_remote_call(self._client)
1612
response = self._call(
1613
'Repository.set_make_working_trees', path, new_value_str)
1614
except errors.UnknownSmartMethod:
1616
self._real_repository.set_make_working_trees(new_value)
1618
if response[0] != 'ok':
1619
raise errors.UnexpectedSmartServerResponse(response)
1622
def signatures(self):
1623
"""Decorate the real repository for now.
1625
In the long term a full blown network facility is needed to avoid
1626
creating a real repository object locally.
1629
return self._real_repository.signatures
1632
def sign_revision(self, revision_id, gpg_strategy):
1634
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1638
"""Decorate the real repository for now.
1640
In the long term a full blown network facility is needed to avoid
1641
creating a real repository object locally.
1644
return self._real_repository.texts
1647
def get_revisions(self, revision_ids):
1649
return self._real_repository.get_revisions(revision_ids)
1651
def supports_rich_root(self):
1652
return self._format.rich_root_data
1654
def iter_reverse_revision_history(self, revision_id):
1656
return self._real_repository.iter_reverse_revision_history(revision_id)
1659
def _serializer(self):
1660
return self._format._serializer
1662
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1664
return self._real_repository.store_revision_signature(
1665
gpg_strategy, plaintext, revision_id)
1667
def add_signature_text(self, revision_id, signature):
1669
return self._real_repository.add_signature_text(revision_id, signature)
1671
def has_signature_for_revision_id(self, revision_id):
1673
return self._real_repository.has_signature_for_revision_id(revision_id)
1675
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1677
return self._real_repository.item_keys_introduced_by(revision_ids,
1678
_files_pb=_files_pb)
1680
def revision_graph_can_have_wrong_parents(self):
1681
# The answer depends on the remote repo format.
1683
return self._real_repository.revision_graph_can_have_wrong_parents()
1685
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1687
return self._real_repository._find_inconsistent_revision_parents(
1690
def _check_for_inconsistent_revision_parents(self):
1692
return self._real_repository._check_for_inconsistent_revision_parents()
1694
def _make_parents_provider(self, other=None):
1695
providers = [self._unstacked_provider]
1696
if other is not None:
1697
providers.insert(0, other)
1698
providers.extend(r._make_parents_provider() for r in
1699
self._fallback_repositories)
1700
return graph.StackedParentsProvider(providers)
1702
def _serialise_search_recipe(self, recipe):
1703
"""Serialise a graph search recipe.
1705
:param recipe: A search recipe (start, stop, count).
1706
:return: Serialised bytes.
1708
start_keys = ' '.join(recipe[1])
1709
stop_keys = ' '.join(recipe[2])
1710
count = str(recipe[3])
1711
return '\n'.join((start_keys, stop_keys, count))
1713
def _serialise_search_result(self, search_result):
1714
if isinstance(search_result, graph.PendingAncestryResult):
1715
parts = ['ancestry-of']
1716
parts.extend(search_result.heads)
1718
recipe = search_result.get_recipe()
1719
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1720
return '\n'.join(parts)
1723
path = self.bzrdir._path_for_remote_call(self._client)
1725
response = self._call('PackRepository.autopack', path)
1726
except errors.UnknownSmartMethod:
1728
self._real_repository._pack_collection.autopack()
1731
if response[0] != 'ok':
1732
raise errors.UnexpectedSmartServerResponse(response)
1735
class RemoteStreamSink(repository.StreamSink):
1737
def _insert_real(self, stream, src_format, resume_tokens):
1738
self.target_repo._ensure_real()
1739
sink = self.target_repo._real_repository._get_sink()
1740
result = sink.insert_stream(stream, src_format, resume_tokens)
1742
self.target_repo.autopack()
1745
def insert_stream(self, stream, src_format, resume_tokens):
1746
target = self.target_repo
1747
target._unstacked_provider.missing_keys.clear()
1748
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1749
if target._lock_token:
1750
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1751
lock_args = (target._lock_token or '',)
1753
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1755
client = target._client
1756
medium = client._medium
1757
path = target.bzrdir._path_for_remote_call(client)
1758
# Probe for the verb to use with an empty stream before sending the
1759
# real stream to it. We do this both to avoid the risk of sending a
1760
# large request that is then rejected, and because we don't want to
1761
# implement a way to buffer, rewind, or restart the stream.
1763
for verb, required_version in candidate_calls:
1764
if medium._is_remote_before(required_version):
1767
# We've already done the probing (and set _is_remote_before) on
1768
# a previous insert.
1771
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1773
response = client.call_with_body_stream(
1774
(verb, path, '') + lock_args, byte_stream)
1775
except errors.UnknownSmartMethod:
1776
medium._remember_remote_is_before(required_version)
1782
return self._insert_real(stream, src_format, resume_tokens)
1783
self._last_inv_record = None
1784
self._last_substream = None
1785
if required_version < (1, 19):
1786
# Remote side doesn't support inventory deltas. Wrap the stream to
1787
# make sure we don't send any. If the stream contains inventory
1788
# deltas we'll interrupt the smart insert_stream request and
1790
stream = self._stop_stream_if_inventory_delta(stream)
1791
byte_stream = smart_repo._stream_to_byte_stream(
1793
resume_tokens = ' '.join(resume_tokens)
1794
response = client.call_with_body_stream(
1795
(verb, path, resume_tokens) + lock_args, byte_stream)
1796
if response[0][0] not in ('ok', 'missing-basis'):
1797
raise errors.UnexpectedSmartServerResponse(response)
1798
if self._last_substream is not None:
1799
# The stream included an inventory-delta record, but the remote
1800
# side isn't new enough to support them. So we need to send the
1801
# rest of the stream via VFS.
1802
self.target_repo.refresh_data()
1803
return self._resume_stream_with_vfs(response, src_format)
1804
if response[0][0] == 'missing-basis':
1805
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1806
resume_tokens = tokens
1807
return resume_tokens, set(missing_keys)
1809
self.target_repo.refresh_data()
1812
def _resume_stream_with_vfs(self, response, src_format):
1813
"""Resume sending a stream via VFS, first resending the record and
1814
substream that couldn't be sent via an insert_stream verb.
1816
if response[0][0] == 'missing-basis':
1817
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1818
# Ignore missing_keys, we haven't finished inserting yet
1821
def resume_substream():
1822
# Yield the substream that was interrupted.
1823
for record in self._last_substream:
1825
self._last_substream = None
1826
def resume_stream():
1827
# Finish sending the interrupted substream
1828
yield ('inventory-deltas', resume_substream())
1829
# Then simply continue sending the rest of the stream.
1830
for substream_kind, substream in self._last_stream:
1831
yield substream_kind, substream
1832
return self._insert_real(resume_stream(), src_format, tokens)
1834
def _stop_stream_if_inventory_delta(self, stream):
1835
"""Normally this just lets the original stream pass-through unchanged.
1837
However if any 'inventory-deltas' substream occurs it will stop
1838
streaming, and store the interrupted substream and stream in
1839
self._last_substream and self._last_stream so that the stream can be
1840
resumed by _resume_stream_with_vfs.
1843
stream_iter = iter(stream)
1844
for substream_kind, substream in stream_iter:
1845
if substream_kind == 'inventory-deltas':
1846
self._last_substream = substream
1847
self._last_stream = stream_iter
1850
yield substream_kind, substream
1853
class RemoteStreamSource(repository.StreamSource):
1854
"""Stream data from a remote server."""
1856
def get_stream(self, search):
1857
if (self.from_repository._fallback_repositories and
1858
self.to_format._fetch_order == 'topological'):
1859
return self._real_stream(self.from_repository, search)
1862
repos = [self.from_repository]
1868
repos.extend(repo._fallback_repositories)
1869
sources.append(repo)
1870
return self.missing_parents_chain(search, sources)
1872
def get_stream_for_missing_keys(self, missing_keys):
1873
self.from_repository._ensure_real()
1874
real_repo = self.from_repository._real_repository
1875
real_source = real_repo._get_source(self.to_format)
1876
return real_source.get_stream_for_missing_keys(missing_keys)
1878
def _real_stream(self, repo, search):
1879
"""Get a stream for search from repo.
1881
This never called RemoteStreamSource.get_stream, and is a heler
1882
for RemoteStreamSource._get_stream to allow getting a stream
1883
reliably whether fallback back because of old servers or trying
1884
to stream from a non-RemoteRepository (which the stacked support
1887
source = repo._get_source(self.to_format)
1888
if isinstance(source, RemoteStreamSource):
1890
source = repo._real_repository._get_source(self.to_format)
1891
return source.get_stream(search)
1893
def _get_stream(self, repo, search):
1894
"""Core worker to get a stream from repo for search.
1896
This is used by both get_stream and the stacking support logic. It
1897
deliberately gets a stream for repo which does not need to be
1898
self.from_repository. In the event that repo is not Remote, or
1899
cannot do a smart stream, a fallback is made to the generic
1900
repository._get_stream() interface, via self._real_stream.
1902
In the event of stacking, streams from _get_stream will not
1903
contain all the data for search - this is normal (see get_stream).
1905
:param repo: A repository.
1906
:param search: A search.
1908
# Fallbacks may be non-smart
1909
if not isinstance(repo, RemoteRepository):
1910
return self._real_stream(repo, search)
1911
client = repo._client
1912
medium = client._medium
1913
path = repo.bzrdir._path_for_remote_call(client)
1914
search_bytes = repo._serialise_search_result(search)
1915
args = (path, self.to_format.network_name())
1917
('Repository.get_stream_1.19', (1, 19)),
1918
('Repository.get_stream', (1, 13))]
1920
for verb, version in candidate_verbs:
1921
if medium._is_remote_before(version):
1924
response = repo._call_with_body_bytes_expecting_body(
1925
verb, args, search_bytes)
1926
except errors.UnknownSmartMethod:
1927
medium._remember_remote_is_before(version)
1929
response_tuple, response_handler = response
1933
return self._real_stream(repo, search)
1934
if response_tuple[0] != 'ok':
1935
raise errors.UnexpectedSmartServerResponse(response_tuple)
1936
byte_stream = response_handler.read_streamed_body()
1937
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1938
if src_format.network_name() != repo._format.network_name():
1939
raise AssertionError(
1940
"Mismatched RemoteRepository and stream src %r, %r" % (
1941
src_format.network_name(), repo._format.network_name()))
1944
def missing_parents_chain(self, search, sources):
1945
"""Chain multiple streams together to handle stacking.
1947
:param search: The overall search to satisfy with streams.
1948
:param sources: A list of Repository objects to query.
1950
self.from_serialiser = self.from_repository._format._serializer
1951
self.seen_revs = set()
1952
self.referenced_revs = set()
1953
# If there are heads in the search, or the key count is > 0, we are not
1955
while not search.is_empty() and len(sources) > 1:
1956
source = sources.pop(0)
1957
stream = self._get_stream(source, search)
1958
for kind, substream in stream:
1959
if kind != 'revisions':
1960
yield kind, substream
1962
yield kind, self.missing_parents_rev_handler(substream)
1963
search = search.refine(self.seen_revs, self.referenced_revs)
1964
self.seen_revs = set()
1965
self.referenced_revs = set()
1966
if not search.is_empty():
1967
for kind, stream in self._get_stream(sources[0], search):
1970
def missing_parents_rev_handler(self, substream):
1971
for content in substream:
1972
revision_bytes = content.get_bytes_as('fulltext')
1973
revision = self.from_serialiser.read_revision_from_string(
1975
self.seen_revs.add(content.key[-1])
1976
self.referenced_revs.update(revision.parent_ids)
1980
class RemoteBranchLockableFiles(LockableFiles):
1981
"""A 'LockableFiles' implementation that talks to a smart server.
1983
This is not a public interface class.
1986
def __init__(self, bzrdir, _client):
1987
self.bzrdir = bzrdir
1988
self._client = _client
1989
self._need_find_modes = True
1990
LockableFiles.__init__(
1991
self, bzrdir.get_branch_transport(None),
1992
'lock', lockdir.LockDir)
1994
def _find_modes(self):
1995
# RemoteBranches don't let the client set the mode of control files.
1996
self._dir_mode = None
1997
self._file_mode = None
2000
class RemoteBranchFormat(branch.BranchFormat):
2002
def __init__(self, network_name=None):
2003
super(RemoteBranchFormat, self).__init__()
2004
self._matchingbzrdir = RemoteBzrDirFormat()
2005
self._matchingbzrdir.set_branch_format(self)
2006
self._custom_format = None
2007
self._network_name = network_name
2009
def __eq__(self, other):
2010
return (isinstance(other, RemoteBranchFormat) and
2011
self.__dict__ == other.__dict__)
2013
def _ensure_real(self):
2014
if self._custom_format is None:
2015
self._custom_format = branch.network_format_registry.get(
2018
def get_format_description(self):
2020
return 'Remote: ' + self._custom_format.get_format_description()
2022
def network_name(self):
2023
return self._network_name
2025
def open(self, a_bzrdir, ignore_fallbacks=False):
2026
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2028
def _vfs_initialize(self, a_bzrdir):
2029
# Initialisation when using a local bzrdir object, or a non-vfs init
2030
# method is not available on the server.
2031
# self._custom_format is always set - the start of initialize ensures
2033
if isinstance(a_bzrdir, RemoteBzrDir):
2034
a_bzrdir._ensure_real()
2035
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2037
# We assume the bzrdir is parameterised; it may not be.
2038
result = self._custom_format.initialize(a_bzrdir)
2039
if (isinstance(a_bzrdir, RemoteBzrDir) and
2040
not isinstance(result, RemoteBranch)):
2041
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2044
def initialize(self, a_bzrdir):
2045
# 1) get the network name to use.
2046
if self._custom_format:
2047
network_name = self._custom_format.network_name()
2049
# Select the current bzrlib default and ask for that.
2050
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2051
reference_format = reference_bzrdir_format.get_branch_format()
2052
self._custom_format = reference_format
2053
network_name = reference_format.network_name()
2054
# Being asked to create on a non RemoteBzrDir:
2055
if not isinstance(a_bzrdir, RemoteBzrDir):
2056
return self._vfs_initialize(a_bzrdir)
2057
medium = a_bzrdir._client._medium
2058
if medium._is_remote_before((1, 13)):
2059
return self._vfs_initialize(a_bzrdir)
2060
# Creating on a remote bzr dir.
2061
# 2) try direct creation via RPC
2062
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2063
verb = 'BzrDir.create_branch'
2065
response = a_bzrdir._call(verb, path, network_name)
2066
except errors.UnknownSmartMethod:
2067
# Fallback - use vfs methods
2068
medium._remember_remote_is_before((1, 13))
2069
return self._vfs_initialize(a_bzrdir)
2070
if response[0] != 'ok':
2071
raise errors.UnexpectedSmartServerResponse(response)
2072
# Turn the response into a RemoteRepository object.
2073
format = RemoteBranchFormat(network_name=response[1])
2074
repo_format = response_tuple_to_repo_format(response[3:])
2075
if response[2] == '':
2076
repo_bzrdir = a_bzrdir
2078
repo_bzrdir = RemoteBzrDir(
2079
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2081
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2082
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2083
format=format, setup_stacking=False)
2084
# XXX: We know this is a new branch, so it must have revno 0, revid
2085
# NULL_REVISION. Creating the branch locked would make this be unable
2086
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2087
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2088
return remote_branch
2090
def make_tags(self, branch):
2092
return self._custom_format.make_tags(branch)
2094
def supports_tags(self):
2095
# Remote branches might support tags, but we won't know until we
2096
# access the real remote branch.
2098
return self._custom_format.supports_tags()
2100
def supports_stacking(self):
2102
return self._custom_format.supports_stacking()
2104
def supports_set_append_revisions_only(self):
2106
return self._custom_format.supports_set_append_revisions_only()
2109
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2110
"""Branch stored on a server accessed by HPSS RPC.
2112
At the moment most operations are mapped down to simple file operations.
2115
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2116
_client=None, format=None, setup_stacking=True):
2117
"""Create a RemoteBranch instance.
2119
:param real_branch: An optional local implementation of the branch
2120
format, usually accessing the data via the VFS.
2121
:param _client: Private parameter for testing.
2122
:param format: A RemoteBranchFormat object, None to create one
2123
automatically. If supplied it should have a network_name already
2125
:param setup_stacking: If True make an RPC call to determine the
2126
stacked (or not) status of the branch. If False assume the branch
2129
# We intentionally don't call the parent class's __init__, because it
2130
# will try to assign to self.tags, which is a property in this subclass.
2131
# And the parent's __init__ doesn't do much anyway.
2132
self.bzrdir = remote_bzrdir
2133
if _client is not None:
2134
self._client = _client
2136
self._client = remote_bzrdir._client
2137
self.repository = remote_repository
2138
if real_branch is not None:
2139
self._real_branch = real_branch
2140
# Give the remote repository the matching real repo.
2141
real_repo = self._real_branch.repository
2142
if isinstance(real_repo, RemoteRepository):
2143
real_repo._ensure_real()
2144
real_repo = real_repo._real_repository
2145
self.repository._set_real_repository(real_repo)
2146
# Give the branch the remote repository to let fast-pathing happen.
2147
self._real_branch.repository = self.repository
2149
self._real_branch = None
2150
# Fill out expected attributes of branch for bzrlib API users.
2151
self._clear_cached_state()
2152
self.base = self.bzrdir.root_transport.base
2153
self._control_files = None
2154
self._lock_mode = None
2155
self._lock_token = None
2156
self._repo_lock_token = None
2157
self._lock_count = 0
2158
self._leave_lock = False
2159
# Setup a format: note that we cannot call _ensure_real until all the
2160
# attributes above are set: This code cannot be moved higher up in this
2163
self._format = RemoteBranchFormat()
2164
if real_branch is not None:
2165
self._format._network_name = \
2166
self._real_branch._format.network_name()
2168
self._format = format
2169
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2170
# branch.open_branch method.
2171
self._real_ignore_fallbacks = not setup_stacking
2172
if not self._format._network_name:
2173
# Did not get from open_branchV2 - old server.
2175
self._format._network_name = \
2176
self._real_branch._format.network_name()
2177
self.tags = self._format.make_tags(self)
2178
# The base class init is not called, so we duplicate this:
2179
hooks = branch.Branch.hooks['open']
2182
self._is_stacked = False
2184
self._setup_stacking()
2186
def _setup_stacking(self):
2187
# configure stacking into the remote repository, by reading it from
2190
fallback_url = self.get_stacked_on_url()
2191
except (errors.NotStacked, errors.UnstackableBranchFormat,
2192
errors.UnstackableRepositoryFormat), e:
2194
self._is_stacked = True
2195
self._activate_fallback_location(fallback_url)
2197
def _get_config(self):
2198
return RemoteBranchConfig(self)
2200
def _get_real_transport(self):
2201
# if we try vfs access, return the real branch's vfs transport
2203
return self._real_branch._transport
2205
_transport = property(_get_real_transport)
2208
return "%s(%s)" % (self.__class__.__name__, self.base)
2212
def _ensure_real(self):
2213
"""Ensure that there is a _real_branch set.
2215
Used before calls to self._real_branch.
2217
if self._real_branch is None:
2218
if not vfs.vfs_enabled():
2219
raise AssertionError('smart server vfs must be enabled '
2220
'to use vfs implementation')
2221
self.bzrdir._ensure_real()
2222
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2223
ignore_fallbacks=self._real_ignore_fallbacks)
2224
if self.repository._real_repository is None:
2225
# Give the remote repository the matching real repo.
2226
real_repo = self._real_branch.repository
2227
if isinstance(real_repo, RemoteRepository):
2228
real_repo._ensure_real()
2229
real_repo = real_repo._real_repository
2230
self.repository._set_real_repository(real_repo)
2231
# Give the real branch the remote repository to let fast-pathing
2233
self._real_branch.repository = self.repository
2234
if self._lock_mode == 'r':
2235
self._real_branch.lock_read()
2236
elif self._lock_mode == 'w':
2237
self._real_branch.lock_write(token=self._lock_token)
2239
def _translate_error(self, err, **context):
2240
self.repository._translate_error(err, branch=self, **context)
2242
def _clear_cached_state(self):
2243
super(RemoteBranch, self)._clear_cached_state()
2244
if self._real_branch is not None:
2245
self._real_branch._clear_cached_state()
2247
def _clear_cached_state_of_remote_branch_only(self):
2248
"""Like _clear_cached_state, but doesn't clear the cache of
2251
This is useful when falling back to calling a method of
2252
self._real_branch that changes state. In that case the underlying
2253
branch changes, so we need to invalidate this RemoteBranch's cache of
2254
it. However, there's no need to invalidate the _real_branch's cache
2255
too, in fact doing so might harm performance.
2257
super(RemoteBranch, self)._clear_cached_state()
2260
def control_files(self):
2261
# Defer actually creating RemoteBranchLockableFiles until its needed,
2262
# because it triggers an _ensure_real that we otherwise might not need.
2263
if self._control_files is None:
2264
self._control_files = RemoteBranchLockableFiles(
2265
self.bzrdir, self._client)
2266
return self._control_files
2268
def _get_checkout_format(self):
2270
return self._real_branch._get_checkout_format()
2272
def get_physical_lock_status(self):
2273
"""See Branch.get_physical_lock_status()."""
2274
# should be an API call to the server, as branches must be lockable.
2276
return self._real_branch.get_physical_lock_status()
2278
def get_stacked_on_url(self):
2279
"""Get the URL this branch is stacked against.
2281
:raises NotStacked: If the branch is not stacked.
2282
:raises UnstackableBranchFormat: If the branch does not support
2284
:raises UnstackableRepositoryFormat: If the repository does not support
2288
# there may not be a repository yet, so we can't use
2289
# self._translate_error, so we can't use self._call either.
2290
response = self._client.call('Branch.get_stacked_on_url',
2291
self._remote_path())
2292
except errors.ErrorFromSmartServer, err:
2293
# there may not be a repository yet, so we can't call through
2294
# its _translate_error
2295
_translate_error(err, branch=self)
2296
except errors.UnknownSmartMethod, err:
2298
return self._real_branch.get_stacked_on_url()
2299
if response[0] != 'ok':
2300
raise errors.UnexpectedSmartServerResponse(response)
2303
def set_stacked_on_url(self, url):
2304
branch.Branch.set_stacked_on_url(self, url)
2306
self._is_stacked = False
2308
self._is_stacked = True
2310
def _vfs_get_tags_bytes(self):
2312
return self._real_branch._get_tags_bytes()
2314
def _get_tags_bytes(self):
2315
medium = self._client._medium
2316
if medium._is_remote_before((1, 13)):
2317
return self._vfs_get_tags_bytes()
2319
response = self._call('Branch.get_tags_bytes', self._remote_path())
2320
except errors.UnknownSmartMethod:
2321
medium._remember_remote_is_before((1, 13))
2322
return self._vfs_get_tags_bytes()
2325
def _vfs_set_tags_bytes(self, bytes):
2327
return self._real_branch._set_tags_bytes(bytes)
2329
def _set_tags_bytes(self, bytes):
2330
medium = self._client._medium
2331
if medium._is_remote_before((1, 18)):
2332
self._vfs_set_tags_bytes(bytes)
2336
self._remote_path(), self._lock_token, self._repo_lock_token)
2337
response = self._call_with_body_bytes(
2338
'Branch.set_tags_bytes', args, bytes)
2339
except errors.UnknownSmartMethod:
2340
medium._remember_remote_is_before((1, 18))
2341
self._vfs_set_tags_bytes(bytes)
2343
def lock_read(self):
2344
self.repository.lock_read()
2345
if not self._lock_mode:
2346
self._note_lock('r')
2347
self._lock_mode = 'r'
2348
self._lock_count = 1
2349
if self._real_branch is not None:
2350
self._real_branch.lock_read()
2352
self._lock_count += 1
2354
def _remote_lock_write(self, token):
2356
branch_token = repo_token = ''
2358
branch_token = token
2359
repo_token = self.repository.lock_write()
2360
self.repository.unlock()
2361
err_context = {'token': token}
2362
response = self._call(
2363
'Branch.lock_write', self._remote_path(), branch_token,
2364
repo_token or '', **err_context)
2365
if response[0] != 'ok':
2366
raise errors.UnexpectedSmartServerResponse(response)
2367
ok, branch_token, repo_token = response
2368
return branch_token, repo_token
2370
def lock_write(self, token=None):
2371
if not self._lock_mode:
2372
self._note_lock('w')
2373
# Lock the branch and repo in one remote call.
2374
remote_tokens = self._remote_lock_write(token)
2375
self._lock_token, self._repo_lock_token = remote_tokens
2376
if not self._lock_token:
2377
raise SmartProtocolError('Remote server did not return a token!')
2378
# Tell the self.repository object that it is locked.
2379
self.repository.lock_write(
2380
self._repo_lock_token, _skip_rpc=True)
2382
if self._real_branch is not None:
2383
self._real_branch.lock_write(token=self._lock_token)
2384
if token is not None:
2385
self._leave_lock = True
2387
self._leave_lock = False
2388
self._lock_mode = 'w'
2389
self._lock_count = 1
2390
elif self._lock_mode == 'r':
2391
raise errors.ReadOnlyTransaction
2393
if token is not None:
2394
# A token was given to lock_write, and we're relocking, so
2395
# check that the given token actually matches the one we
2397
if token != self._lock_token:
2398
raise errors.TokenMismatch(token, self._lock_token)
2399
self._lock_count += 1
2400
# Re-lock the repository too.
2401
self.repository.lock_write(self._repo_lock_token)
2402
return self._lock_token or None
2404
def _unlock(self, branch_token, repo_token):
2405
err_context = {'token': str((branch_token, repo_token))}
2406
response = self._call(
2407
'Branch.unlock', self._remote_path(), branch_token,
2408
repo_token or '', **err_context)
2409
if response == ('ok',):
2411
raise errors.UnexpectedSmartServerResponse(response)
2413
@only_raises(errors.LockNotHeld, errors.LockBroken)
2416
self._lock_count -= 1
2417
if not self._lock_count:
2418
self._clear_cached_state()
2419
mode = self._lock_mode
2420
self._lock_mode = None
2421
if self._real_branch is not None:
2422
if (not self._leave_lock and mode == 'w' and
2423
self._repo_lock_token):
2424
# If this RemoteBranch will remove the physical lock
2425
# for the repository, make sure the _real_branch
2426
# doesn't do it first. (Because the _real_branch's
2427
# repository is set to be the RemoteRepository.)
2428
self._real_branch.repository.leave_lock_in_place()
2429
self._real_branch.unlock()
2431
# Only write-locked branched need to make a remote method
2432
# call to perform the unlock.
2434
if not self._lock_token:
2435
raise AssertionError('Locked, but no token!')
2436
branch_token = self._lock_token
2437
repo_token = self._repo_lock_token
2438
self._lock_token = None
2439
self._repo_lock_token = None
2440
if not self._leave_lock:
2441
self._unlock(branch_token, repo_token)
2443
self.repository.unlock()
2445
def break_lock(self):
2447
return self._real_branch.break_lock()
2449
def leave_lock_in_place(self):
2450
if not self._lock_token:
2451
raise NotImplementedError(self.leave_lock_in_place)
2452
self._leave_lock = True
2454
def dont_leave_lock_in_place(self):
2455
if not self._lock_token:
2456
raise NotImplementedError(self.dont_leave_lock_in_place)
2457
self._leave_lock = False
2460
def get_rev_id(self, revno, history=None):
2462
return _mod_revision.NULL_REVISION
2463
last_revision_info = self.last_revision_info()
2464
ok, result = self.repository.get_rev_id_for_revno(
2465
revno, last_revision_info)
2468
missing_parent = result[1]
2469
# Either the revision named by the server is missing, or its parent
2470
# is. Call get_parent_map to determine which, so that we report a
2472
parent_map = self.repository.get_parent_map([missing_parent])
2473
if missing_parent in parent_map:
2474
missing_parent = parent_map[missing_parent]
2475
raise errors.RevisionNotPresent(missing_parent, self.repository)
2477
def _last_revision_info(self):
2478
response = self._call('Branch.last_revision_info', self._remote_path())
2479
if response[0] != 'ok':
2480
raise SmartProtocolError('unexpected response code %s' % (response,))
2481
revno = int(response[1])
2482
last_revision = response[2]
2483
return (revno, last_revision)
2485
def _gen_revision_history(self):
2486
"""See Branch._gen_revision_history()."""
2487
if self._is_stacked:
2489
return self._real_branch._gen_revision_history()
2490
response_tuple, response_handler = self._call_expecting_body(
2491
'Branch.revision_history', self._remote_path())
2492
if response_tuple[0] != 'ok':
2493
raise errors.UnexpectedSmartServerResponse(response_tuple)
2494
result = response_handler.read_body_bytes().split('\x00')
2499
def _remote_path(self):
2500
return self.bzrdir._path_for_remote_call(self._client)
2502
def _set_last_revision_descendant(self, revision_id, other_branch,
2503
allow_diverged=False, allow_overwrite_descendant=False):
2504
# This performs additional work to meet the hook contract; while its
2505
# undesirable, we have to synthesise the revno to call the hook, and
2506
# not calling the hook is worse as it means changes can't be prevented.
2507
# Having calculated this though, we can't just call into
2508
# set_last_revision_info as a simple call, because there is a set_rh
2509
# hook that some folk may still be using.
2510
old_revno, old_revid = self.last_revision_info()
2511
history = self._lefthand_history(revision_id)
2512
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2513
err_context = {'other_branch': other_branch}
2514
response = self._call('Branch.set_last_revision_ex',
2515
self._remote_path(), self._lock_token, self._repo_lock_token,
2516
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2518
self._clear_cached_state()
2519
if len(response) != 3 and response[0] != 'ok':
2520
raise errors.UnexpectedSmartServerResponse(response)
2521
new_revno, new_revision_id = response[1:]
2522
self._last_revision_info_cache = new_revno, new_revision_id
2523
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2524
if self._real_branch is not None:
2525
cache = new_revno, new_revision_id
2526
self._real_branch._last_revision_info_cache = cache
2528
def _set_last_revision(self, revision_id):
2529
old_revno, old_revid = self.last_revision_info()
2530
# This performs additional work to meet the hook contract; while its
2531
# undesirable, we have to synthesise the revno to call the hook, and
2532
# not calling the hook is worse as it means changes can't be prevented.
2533
# Having calculated this though, we can't just call into
2534
# set_last_revision_info as a simple call, because there is a set_rh
2535
# hook that some folk may still be using.
2536
history = self._lefthand_history(revision_id)
2537
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2538
self._clear_cached_state()
2539
response = self._call('Branch.set_last_revision',
2540
self._remote_path(), self._lock_token, self._repo_lock_token,
2542
if response != ('ok',):
2543
raise errors.UnexpectedSmartServerResponse(response)
2544
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2547
def set_revision_history(self, rev_history):
2548
# Send just the tip revision of the history; the server will generate
2549
# the full history from that. If the revision doesn't exist in this
2550
# branch, NoSuchRevision will be raised.
2551
if rev_history == []:
2554
rev_id = rev_history[-1]
2555
self._set_last_revision(rev_id)
2556
for hook in branch.Branch.hooks['set_rh']:
2557
hook(self, rev_history)
2558
self._cache_revision_history(rev_history)
2560
def _get_parent_location(self):
2561
medium = self._client._medium
2562
if medium._is_remote_before((1, 13)):
2563
return self._vfs_get_parent_location()
2565
response = self._call('Branch.get_parent', self._remote_path())
2566
except errors.UnknownSmartMethod:
2567
medium._remember_remote_is_before((1, 13))
2568
return self._vfs_get_parent_location()
2569
if len(response) != 1:
2570
raise errors.UnexpectedSmartServerResponse(response)
2571
parent_location = response[0]
2572
if parent_location == '':
2574
return parent_location
2576
def _vfs_get_parent_location(self):
2578
return self._real_branch._get_parent_location()
2580
def _set_parent_location(self, url):
2581
medium = self._client._medium
2582
if medium._is_remote_before((1, 15)):
2583
return self._vfs_set_parent_location(url)
2585
call_url = url or ''
2586
if type(call_url) is not str:
2587
raise AssertionError('url must be a str or None (%s)' % url)
2588
response = self._call('Branch.set_parent_location',
2589
self._remote_path(), self._lock_token, self._repo_lock_token,
2591
except errors.UnknownSmartMethod:
2592
medium._remember_remote_is_before((1, 15))
2593
return self._vfs_set_parent_location(url)
2595
raise errors.UnexpectedSmartServerResponse(response)
2597
def _vfs_set_parent_location(self, url):
2599
return self._real_branch._set_parent_location(url)
2602
def pull(self, source, overwrite=False, stop_revision=None,
2604
self._clear_cached_state_of_remote_branch_only()
2606
return self._real_branch.pull(
2607
source, overwrite=overwrite, stop_revision=stop_revision,
2608
_override_hook_target=self, **kwargs)
2611
def push(self, target, overwrite=False, stop_revision=None):
2613
return self._real_branch.push(
2614
target, overwrite=overwrite, stop_revision=stop_revision,
2615
_override_hook_source_branch=self)
2617
def is_locked(self):
2618
return self._lock_count >= 1
2621
def revision_id_to_revno(self, revision_id):
2623
return self._real_branch.revision_id_to_revno(revision_id)
2626
def set_last_revision_info(self, revno, revision_id):
2627
# XXX: These should be returned by the set_last_revision_info verb
2628
old_revno, old_revid = self.last_revision_info()
2629
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2630
revision_id = ensure_null(revision_id)
2632
response = self._call('Branch.set_last_revision_info',
2633
self._remote_path(), self._lock_token, self._repo_lock_token,
2634
str(revno), revision_id)
2635
except errors.UnknownSmartMethod:
2637
self._clear_cached_state_of_remote_branch_only()
2638
self._real_branch.set_last_revision_info(revno, revision_id)
2639
self._last_revision_info_cache = revno, revision_id
2641
if response == ('ok',):
2642
self._clear_cached_state()
2643
self._last_revision_info_cache = revno, revision_id
2644
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2645
# Update the _real_branch's cache too.
2646
if self._real_branch is not None:
2647
cache = self._last_revision_info_cache
2648
self._real_branch._last_revision_info_cache = cache
2650
raise errors.UnexpectedSmartServerResponse(response)
2653
def generate_revision_history(self, revision_id, last_rev=None,
2655
medium = self._client._medium
2656
if not medium._is_remote_before((1, 6)):
2657
# Use a smart method for 1.6 and above servers
2659
self._set_last_revision_descendant(revision_id, other_branch,
2660
allow_diverged=True, allow_overwrite_descendant=True)
2662
except errors.UnknownSmartMethod:
2663
medium._remember_remote_is_before((1, 6))
2664
self._clear_cached_state_of_remote_branch_only()
2665
self.set_revision_history(self._lefthand_history(revision_id,
2666
last_rev=last_rev,other_branch=other_branch))
2668
def set_push_location(self, location):
2670
return self._real_branch.set_push_location(location)
2673
class RemoteConfig(object):
2674
"""A Config that reads and writes from smart verbs.
2676
It is a low-level object that considers config data to be name/value pairs
2677
that may be associated with a section. Assigning meaning to the these
2678
values is done at higher levels like bzrlib.config.TreeConfig.
2681
def get_option(self, name, section=None, default=None):
2682
"""Return the value associated with a named option.
2684
:param name: The name of the value
2685
:param section: The section the option is in (if any)
2686
:param default: The value to return if the value is not set
2687
:return: The value or default value
2690
configobj = self._get_configobj()
2692
section_obj = configobj
2695
section_obj = configobj[section]
2698
return section_obj.get(name, default)
2699
except errors.UnknownSmartMethod:
2700
return self._vfs_get_option(name, section, default)
2702
def _response_to_configobj(self, response):
2703
if len(response[0]) and response[0][0] != 'ok':
2704
raise errors.UnexpectedSmartServerResponse(response)
2705
lines = response[1].read_body_bytes().splitlines()
2706
return config.ConfigObj(lines, encoding='utf-8')
2709
class RemoteBranchConfig(RemoteConfig):
2710
"""A RemoteConfig for Branches."""
2712
def __init__(self, branch):
2713
self._branch = branch
2715
def _get_configobj(self):
2716
path = self._branch._remote_path()
2717
response = self._branch._client.call_expecting_body(
2718
'Branch.get_config_file', path)
2719
return self._response_to_configobj(response)
2721
def set_option(self, value, name, section=None):
2722
"""Set the value associated with a named option.
2724
:param value: The value to set
2725
:param name: The name of the value to set
2726
:param section: The section the option is in (if any)
2728
medium = self._branch._client._medium
2729
if medium._is_remote_before((1, 14)):
2730
return self._vfs_set_option(value, name, section)
2732
path = self._branch._remote_path()
2733
response = self._branch._client.call('Branch.set_config_option',
2734
path, self._branch._lock_token, self._branch._repo_lock_token,
2735
value.encode('utf8'), name, section or '')
2736
except errors.UnknownSmartMethod:
2737
medium._remember_remote_is_before((1, 14))
2738
return self._vfs_set_option(value, name, section)
2740
raise errors.UnexpectedSmartServerResponse(response)
2742
def _real_object(self):
2743
self._branch._ensure_real()
2744
return self._branch._real_branch
2746
def _vfs_set_option(self, value, name, section=None):
2747
return self._real_object()._get_config().set_option(
2748
value, name, section)
2751
class RemoteBzrDirConfig(RemoteConfig):
2752
"""A RemoteConfig for BzrDirs."""
2754
def __init__(self, bzrdir):
2755
self._bzrdir = bzrdir
2757
def _get_configobj(self):
2758
medium = self._bzrdir._client._medium
2759
verb = 'BzrDir.get_config_file'
2760
if medium._is_remote_before((1, 15)):
2761
raise errors.UnknownSmartMethod(verb)
2762
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2763
response = self._bzrdir._call_expecting_body(
2765
return self._response_to_configobj(response)
2767
def _vfs_get_option(self, name, section, default):
2768
return self._real_object()._get_config().get_option(
2769
name, section, default)
2771
def set_option(self, value, name, section=None):
2772
"""Set the value associated with a named option.
2774
:param value: The value to set
2775
:param name: The name of the value to set
2776
:param section: The section the option is in (if any)
2778
return self._real_object()._get_config().set_option(
2779
value, name, section)
2781
def _real_object(self):
2782
self._bzrdir._ensure_real()
2783
return self._bzrdir._real_bzrdir
2787
def _extract_tar(tar, to_dir):
2788
"""Extract all the contents of a tarfile object.
2790
A replacement for extractall, which is not present in python2.4
2793
tar.extract(tarinfo, to_dir)
2796
def _translate_error(err, **context):
2797
"""Translate an ErrorFromSmartServer into a more useful error.
2799
Possible context keys:
2807
If the error from the server doesn't match a known pattern, then
2808
UnknownErrorFromSmartServer is raised.
2812
return context[name]
2813
except KeyError, key_err:
2814
mutter('Missing key %r in context %r', key_err.args[0], context)
2817
"""Get the path from the context if present, otherwise use first error
2821
return context['path']
2822
except KeyError, key_err:
2824
return err.error_args[0]
2825
except IndexError, idx_err:
2827
'Missing key %r in context %r', key_err.args[0], context)
2830
if err.error_verb == 'IncompatibleRepositories':
2831
raise errors.IncompatibleRepositories(err.error_args[0],
2832
err.error_args[1], err.error_args[2])
2833
elif err.error_verb == 'NoSuchRevision':
2834
raise NoSuchRevision(find('branch'), err.error_args[0])
2835
elif err.error_verb == 'nosuchrevision':
2836
raise NoSuchRevision(find('repository'), err.error_args[0])
2837
elif err.error_verb == 'nobranch':
2838
if len(err.error_args) >= 1:
2839
extra = err.error_args[0]
2842
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2844
elif err.error_verb == 'norepository':
2845
raise errors.NoRepositoryPresent(find('bzrdir'))
2846
elif err.error_verb == 'LockContention':
2847
raise errors.LockContention('(remote lock)')
2848
elif err.error_verb == 'UnlockableTransport':
2849
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2850
elif err.error_verb == 'LockFailed':
2851
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2852
elif err.error_verb == 'TokenMismatch':
2853
raise errors.TokenMismatch(find('token'), '(remote token)')
2854
elif err.error_verb == 'Diverged':
2855
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2856
elif err.error_verb == 'TipChangeRejected':
2857
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2858
elif err.error_verb == 'UnstackableBranchFormat':
2859
raise errors.UnstackableBranchFormat(*err.error_args)
2860
elif err.error_verb == 'UnstackableRepositoryFormat':
2861
raise errors.UnstackableRepositoryFormat(*err.error_args)
2862
elif err.error_verb == 'NotStacked':
2863
raise errors.NotStacked(branch=find('branch'))
2864
elif err.error_verb == 'PermissionDenied':
2866
if len(err.error_args) >= 2:
2867
extra = err.error_args[1]
2870
raise errors.PermissionDenied(path, extra=extra)
2871
elif err.error_verb == 'ReadError':
2873
raise errors.ReadError(path)
2874
elif err.error_verb == 'NoSuchFile':
2876
raise errors.NoSuchFile(path)
2877
elif err.error_verb == 'FileExists':
2878
raise errors.FileExists(err.error_args[0])
2879
elif err.error_verb == 'DirectoryNotEmpty':
2880
raise errors.DirectoryNotEmpty(err.error_args[0])
2881
elif err.error_verb == 'ShortReadvError':
2882
args = err.error_args
2883
raise errors.ShortReadvError(
2884
args[0], int(args[1]), int(args[2]), int(args[3]))
2885
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2886
encoding = str(err.error_args[0]) # encoding must always be a string
2887
val = err.error_args[1]
2888
start = int(err.error_args[2])
2889
end = int(err.error_args[3])
2890
reason = str(err.error_args[4]) # reason must always be a string
2891
if val.startswith('u:'):
2892
val = val[2:].decode('utf-8')
2893
elif val.startswith('s:'):
2894
val = val[2:].decode('base64')
2895
if err.error_verb == 'UnicodeDecodeError':
2896
raise UnicodeDecodeError(encoding, val, start, end, reason)
2897
elif err.error_verb == 'UnicodeEncodeError':
2898
raise UnicodeEncodeError(encoding, val, start, end, reason)
2899
elif err.error_verb == 'ReadOnlyError':
2900
raise errors.TransportNotPossible('readonly transport')
2901
raise errors.UnknownErrorFromSmartServer(err)