1
# Copyright (C) 2006, 2007, 2008 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
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
33
revision as _mod_revision,
36
from bzrlib.branch import BranchReferenceFormat
37
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
38
from bzrlib.decorators import needs_read_lock, needs_write_lock
39
from bzrlib.errors import (
43
from bzrlib.lockable_files import LockableFiles
44
from bzrlib.smart import client, vfs, repository as smart_repo
45
from bzrlib.revision import ensure_null, NULL_REVISION
46
from bzrlib.trace import mutter, note, warning
49
class _RpcHelper(object):
50
"""Mixin class that helps with issuing RPCs."""
52
def _call(self, method, *args, **err_context):
54
return self._client.call(method, *args)
55
except errors.ErrorFromSmartServer, err:
56
self._translate_error(err, **err_context)
58
def _call_expecting_body(self, method, *args, **err_context):
60
return self._client.call_expecting_body(method, *args)
61
except errors.ErrorFromSmartServer, err:
62
self._translate_error(err, **err_context)
64
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
67
return self._client.call_with_body_bytes_expecting_body(
68
method, args, body_bytes)
69
except errors.ErrorFromSmartServer, err:
70
self._translate_error(err, **err_context)
73
def response_tuple_to_repo_format(response):
74
"""Convert a response tuple describing a repository format to a format."""
75
format = RemoteRepositoryFormat()
76
format._rich_root_data = (response[0] == 'yes')
77
format._supports_tree_reference = (response[1] == 'yes')
78
format._supports_external_lookups = (response[2] == 'yes')
79
format._network_name = response[3]
83
# Note: RemoteBzrDirFormat is in bzrdir.py
85
class RemoteBzrDir(BzrDir, _RpcHelper):
86
"""Control directory on a remote server, accessed via bzr:// or similar."""
88
def __init__(self, transport, format, _client=None):
89
"""Construct a RemoteBzrDir.
91
:param _client: Private parameter for testing. Disables probing and the
94
BzrDir.__init__(self, transport, format)
95
# this object holds a delegated bzrdir that uses file-level operations
96
# to talk to the other side
97
self._real_bzrdir = None
98
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
99
# create_branch for details.
100
self._next_open_branch_result = None
103
medium = transport.get_smart_medium()
104
self._client = client._SmartClient(medium)
106
self._client = _client
109
path = self._path_for_remote_call(self._client)
110
response = self._call('BzrDir.open', path)
111
if response not in [('yes',), ('no',)]:
112
raise errors.UnexpectedSmartServerResponse(response)
113
if response == ('no',):
114
raise errors.NotBranchError(path=transport.base)
116
def _ensure_real(self):
117
"""Ensure that there is a _real_bzrdir set.
119
Used before calls to self._real_bzrdir.
121
if not self._real_bzrdir:
122
self._real_bzrdir = BzrDir.open_from_transport(
123
self.root_transport, _server_formats=False)
124
self._format._network_name = \
125
self._real_bzrdir._format.network_name()
127
def _translate_error(self, err, **context):
128
_translate_error(err, bzrdir=self, **context)
130
def break_lock(self):
131
# Prevent aliasing problems in the next_open_branch_result cache.
132
# See create_branch for rationale.
133
self._next_open_branch_result = None
134
return BzrDir.break_lock(self)
136
def _vfs_cloning_metadir(self, require_stacking=False):
138
return self._real_bzrdir.cloning_metadir(
139
require_stacking=require_stacking)
141
def cloning_metadir(self, require_stacking=False):
142
medium = self._client._medium
143
if medium._is_remote_before((1, 13)):
144
return self._vfs_cloning_metadir(require_stacking=require_stacking)
145
verb = 'BzrDir.cloning_metadir'
150
path = self._path_for_remote_call(self._client)
152
response = self._call(verb, path, stacking)
153
except errors.UnknownSmartMethod:
154
medium._remember_remote_is_before((1, 13))
155
return self._vfs_cloning_metadir(require_stacking=require_stacking)
156
except errors.UnknownErrorFromSmartServer, err:
157
if err.error_tuple != ('BranchReference',):
159
# We need to resolve the branch reference to determine the
160
# cloning_metadir. This causes unnecessary RPCs to open the
161
# referenced branch (and bzrdir, etc) but only when the caller
162
# didn't already resolve the branch reference.
163
referenced_branch = self.open_branch()
164
return referenced_branch.bzrdir.cloning_metadir()
165
if len(response) != 3:
166
raise errors.UnexpectedSmartServerResponse(response)
167
control_name, repo_name, branch_info = response
168
if len(branch_info) != 2:
169
raise errors.UnexpectedSmartServerResponse(response)
170
branch_ref, branch_name = branch_info
171
format = bzrdir.network_format_registry.get(control_name)
173
format.repository_format = repository.network_format_registry.get(
175
if branch_ref == 'ref':
176
# XXX: we need possible_transports here to avoid reopening the
177
# connection to the referenced location
178
ref_bzrdir = BzrDir.open(branch_name)
179
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
180
format.set_branch_format(branch_format)
181
elif branch_ref == 'branch':
183
format.set_branch_format(
184
branch.network_format_registry.get(branch_name))
186
raise errors.UnexpectedSmartServerResponse(response)
189
def create_repository(self, shared=False):
190
# as per meta1 formats - just delegate to the format object which may
192
result = self._format.repository_format.initialize(self, shared)
193
if not isinstance(result, RemoteRepository):
194
return self.open_repository()
198
def destroy_repository(self):
199
"""See BzrDir.destroy_repository"""
201
self._real_bzrdir.destroy_repository()
203
def create_branch(self):
204
# as per meta1 formats - just delegate to the format object which may
206
real_branch = self._format.get_branch_format().initialize(self)
207
if not isinstance(real_branch, RemoteBranch):
208
result = RemoteBranch(self, self.find_repository(), real_branch)
211
# BzrDir.clone_on_transport() uses the result of create_branch but does
212
# not return it to its callers; we save approximately 8% of our round
213
# trips by handing the branch we created back to the first caller to
214
# open_branch rather than probing anew. Long term we need a API in
215
# bzrdir that doesn't discard result objects (like result_branch).
217
self._next_open_branch_result = result
220
def destroy_branch(self):
221
"""See BzrDir.destroy_branch"""
223
self._real_bzrdir.destroy_branch()
224
self._next_open_branch_result = None
226
def create_workingtree(self, revision_id=None, from_branch=None):
227
raise errors.NotLocalUrl(self.transport.base)
229
def find_branch_format(self):
230
"""Find the branch 'format' for this bzrdir.
232
This might be a synthetic object for e.g. RemoteBranch and SVN.
234
b = self.open_branch()
237
def get_branch_reference(self):
238
"""See BzrDir.get_branch_reference()."""
239
response = self._get_branch_reference()
240
if response[0] == 'ref':
245
def _get_branch_reference(self):
246
path = self._path_for_remote_call(self._client)
247
medium = self._client._medium
248
if not medium._is_remote_before((1, 13)):
250
response = self._call('BzrDir.open_branchV2', path)
251
if response[0] not in ('ref', 'branch'):
252
raise errors.UnexpectedSmartServerResponse(response)
254
except errors.UnknownSmartMethod:
255
medium._remember_remote_is_before((1, 13))
256
response = self._call('BzrDir.open_branch', path)
257
if response[0] != 'ok':
258
raise errors.UnexpectedSmartServerResponse(response)
259
if response[1] != '':
260
return ('ref', response[1])
262
return ('branch', '')
264
def _get_tree_branch(self):
265
"""See BzrDir._get_tree_branch()."""
266
return None, self.open_branch()
268
def open_branch(self, _unsupported=False, ignore_fallbacks=False):
270
raise NotImplementedError('unsupported flag support not implemented yet.')
271
if self._next_open_branch_result is not None:
272
# See create_branch for details.
273
result = self._next_open_branch_result
274
self._next_open_branch_result = None
276
response = self._get_branch_reference()
277
if response[0] == 'ref':
278
# a branch reference, use the existing BranchReference logic.
279
format = BranchReferenceFormat()
280
return format.open(self, _found=True, location=response[1],
281
ignore_fallbacks=ignore_fallbacks)
282
branch_format_name = response[1]
283
if not branch_format_name:
284
branch_format_name = None
285
format = RemoteBranchFormat(network_name=branch_format_name)
286
return RemoteBranch(self, self.find_repository(), format=format,
287
setup_stacking=not ignore_fallbacks)
289
def _open_repo_v1(self, path):
290
verb = 'BzrDir.find_repository'
291
response = self._call(verb, path)
292
if response[0] != 'ok':
293
raise errors.UnexpectedSmartServerResponse(response)
294
# servers that only support the v1 method don't support external
297
repo = self._real_bzrdir.open_repository()
298
response = response + ('no', repo._format.network_name())
299
return response, repo
301
def _open_repo_v2(self, path):
302
verb = 'BzrDir.find_repositoryV2'
303
response = self._call(verb, path)
304
if response[0] != 'ok':
305
raise errors.UnexpectedSmartServerResponse(response)
307
repo = self._real_bzrdir.open_repository()
308
response = response + (repo._format.network_name(),)
309
return response, repo
311
def _open_repo_v3(self, path):
312
verb = 'BzrDir.find_repositoryV3'
313
medium = self._client._medium
314
if medium._is_remote_before((1, 13)):
315
raise errors.UnknownSmartMethod(verb)
317
response = self._call(verb, path)
318
except errors.UnknownSmartMethod:
319
medium._remember_remote_is_before((1, 13))
321
if response[0] != 'ok':
322
raise errors.UnexpectedSmartServerResponse(response)
323
return response, None
325
def open_repository(self):
326
path = self._path_for_remote_call(self._client)
328
for probe in [self._open_repo_v3, self._open_repo_v2,
331
response, real_repo = probe(path)
333
except errors.UnknownSmartMethod:
336
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
337
if response[0] != 'ok':
338
raise errors.UnexpectedSmartServerResponse(response)
339
if len(response) != 6:
340
raise SmartProtocolError('incorrect response length %s' % (response,))
341
if response[1] == '':
342
# repo is at this dir.
343
format = response_tuple_to_repo_format(response[2:])
344
# Used to support creating a real format instance when needed.
345
format._creating_bzrdir = self
346
remote_repo = RemoteRepository(self, format)
347
format._creating_repo = remote_repo
348
if real_repo is not None:
349
remote_repo._set_real_repository(real_repo)
352
raise errors.NoRepositoryPresent(self)
354
def open_workingtree(self, recommend_upgrade=True):
356
if self._real_bzrdir.has_workingtree():
357
raise errors.NotLocalUrl(self.root_transport)
359
raise errors.NoWorkingTree(self.root_transport.base)
361
def _path_for_remote_call(self, client):
362
"""Return the path to be used for this bzrdir in a remote call."""
363
return client.remote_path_from_transport(self.root_transport)
365
def get_branch_transport(self, branch_format):
367
return self._real_bzrdir.get_branch_transport(branch_format)
369
def get_repository_transport(self, repository_format):
371
return self._real_bzrdir.get_repository_transport(repository_format)
373
def get_workingtree_transport(self, workingtree_format):
375
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
377
def can_convert_format(self):
378
"""Upgrading of remote bzrdirs is not supported yet."""
381
def needs_format_conversion(self, format=None):
382
"""Upgrading of remote bzrdirs is not supported yet."""
384
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
385
% 'needs_format_conversion(format=None)')
388
def clone(self, url, revision_id=None, force_new_repo=False,
389
preserve_stacking=False):
391
return self._real_bzrdir.clone(url, revision_id=revision_id,
392
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
394
def _get_config(self):
395
return RemoteBzrDirConfig(self)
398
class RemoteRepositoryFormat(repository.RepositoryFormat):
399
"""Format for repositories accessed over a _SmartClient.
401
Instances of this repository are represented by RemoteRepository
404
The RemoteRepositoryFormat is parameterized during construction
405
to reflect the capabilities of the real, remote format. Specifically
406
the attributes rich_root_data and supports_tree_reference are set
407
on a per instance basis, and are not set (and should not be) at
410
:ivar _custom_format: If set, a specific concrete repository format that
411
will be used when initializing a repository with this
412
RemoteRepositoryFormat.
413
:ivar _creating_repo: If set, the repository object that this
414
RemoteRepositoryFormat was created for: it can be called into
415
to obtain data like the network name.
418
_matchingbzrdir = RemoteBzrDirFormat()
421
repository.RepositoryFormat.__init__(self)
422
self._custom_format = None
423
self._network_name = None
424
self._creating_bzrdir = None
425
self._supports_chks = None
426
self._supports_external_lookups = None
427
self._supports_tree_reference = None
428
self._rich_root_data = None
431
def fast_deltas(self):
433
return self._custom_format.fast_deltas
436
def rich_root_data(self):
437
if self._rich_root_data is None:
439
self._rich_root_data = self._custom_format.rich_root_data
440
return self._rich_root_data
443
def supports_chks(self):
444
if self._supports_chks is None:
446
self._supports_chks = self._custom_format.supports_chks
447
return self._supports_chks
450
def supports_external_lookups(self):
451
if self._supports_external_lookups is None:
453
self._supports_external_lookups = \
454
self._custom_format.supports_external_lookups
455
return self._supports_external_lookups
458
def supports_tree_reference(self):
459
if self._supports_tree_reference is None:
461
self._supports_tree_reference = \
462
self._custom_format.supports_tree_reference
463
return self._supports_tree_reference
465
def _vfs_initialize(self, a_bzrdir, shared):
466
"""Helper for common code in initialize."""
467
if self._custom_format:
468
# Custom format requested
469
result = self._custom_format.initialize(a_bzrdir, shared=shared)
470
elif self._creating_bzrdir is not None:
471
# Use the format that the repository we were created to back
473
prior_repo = self._creating_bzrdir.open_repository()
474
prior_repo._ensure_real()
475
result = prior_repo._real_repository._format.initialize(
476
a_bzrdir, shared=shared)
478
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
479
# support remote initialization.
480
# We delegate to a real object at this point (as RemoteBzrDir
481
# delegate to the repository format which would lead to infinite
482
# recursion if we just called a_bzrdir.create_repository.
483
a_bzrdir._ensure_real()
484
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
485
if not isinstance(result, RemoteRepository):
486
return self.open(a_bzrdir)
490
def initialize(self, a_bzrdir, shared=False):
491
# Being asked to create on a non RemoteBzrDir:
492
if not isinstance(a_bzrdir, RemoteBzrDir):
493
return self._vfs_initialize(a_bzrdir, shared)
494
medium = a_bzrdir._client._medium
495
if medium._is_remote_before((1, 13)):
496
return self._vfs_initialize(a_bzrdir, shared)
497
# Creating on a remote bzr dir.
498
# 1) get the network name to use.
499
if self._custom_format:
500
network_name = self._custom_format.network_name()
501
elif self._network_name:
502
network_name = self._network_name
504
# Select the current bzrlib default and ask for that.
505
reference_bzrdir_format = bzrdir.format_registry.get('default')()
506
reference_format = reference_bzrdir_format.repository_format
507
network_name = reference_format.network_name()
508
# 2) try direct creation via RPC
509
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
510
verb = 'BzrDir.create_repository'
516
response = a_bzrdir._call(verb, path, network_name, shared_str)
517
except errors.UnknownSmartMethod:
518
# Fallback - use vfs methods
519
medium._remember_remote_is_before((1, 13))
520
return self._vfs_initialize(a_bzrdir, shared)
522
# Turn the response into a RemoteRepository object.
523
format = response_tuple_to_repo_format(response[1:])
524
# Used to support creating a real format instance when needed.
525
format._creating_bzrdir = a_bzrdir
526
remote_repo = RemoteRepository(a_bzrdir, format)
527
format._creating_repo = remote_repo
530
def open(self, a_bzrdir):
531
if not isinstance(a_bzrdir, RemoteBzrDir):
532
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
533
return a_bzrdir.open_repository()
535
def _ensure_real(self):
536
if self._custom_format is None:
537
self._custom_format = repository.network_format_registry.get(
541
def _fetch_order(self):
543
return self._custom_format._fetch_order
546
def _fetch_uses_deltas(self):
548
return self._custom_format._fetch_uses_deltas
551
def _fetch_reconcile(self):
553
return self._custom_format._fetch_reconcile
555
def get_format_description(self):
556
return 'bzr remote repository'
558
def __eq__(self, other):
559
return self.__class__ is other.__class__
561
def check_conversion_target(self, target_format):
562
if self.rich_root_data and not target_format.rich_root_data:
563
raise errors.BadConversionTarget(
564
'Does not support rich root data.', target_format)
565
if (self.supports_tree_reference and
566
not getattr(target_format, 'supports_tree_reference', False)):
567
raise errors.BadConversionTarget(
568
'Does not support nested trees', target_format)
570
def network_name(self):
571
if self._network_name:
572
return self._network_name
573
self._creating_repo._ensure_real()
574
return self._creating_repo._real_repository._format.network_name()
577
def pack_compresses(self):
579
return self._custom_format.pack_compresses
582
def _serializer(self):
584
return self._custom_format._serializer
587
class RemoteRepository(_RpcHelper):
588
"""Repository accessed over rpc.
590
For the moment most operations are performed using local transport-backed
594
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
595
"""Create a RemoteRepository instance.
597
:param remote_bzrdir: The bzrdir hosting this repository.
598
:param format: The RemoteFormat object to use.
599
:param real_repository: If not None, a local implementation of the
600
repository logic for the repository, usually accessing the data
602
:param _client: Private testing parameter - override the smart client
603
to be used by the repository.
606
self._real_repository = real_repository
608
self._real_repository = None
609
self.bzrdir = remote_bzrdir
611
self._client = remote_bzrdir._client
613
self._client = _client
614
self._format = format
615
self._lock_mode = None
616
self._lock_token = None
618
self._leave_lock = False
619
# Cache of revision parents; misses are cached during read locks, and
620
# write locks when no _real_repository has been set.
621
self._unstacked_provider = graph.CachingParentsProvider(
622
get_parent_map=self._get_parent_map_rpc)
623
self._unstacked_provider.disable_cache()
625
# These depend on the actual remote format, so force them off for
626
# maximum compatibility. XXX: In future these should depend on the
627
# remote repository instance, but this is irrelevant until we perform
628
# reconcile via an RPC call.
629
self._reconcile_does_inventory_gc = False
630
self._reconcile_fixes_text_parents = False
631
self._reconcile_backsup_inventory = False
632
self.base = self.bzrdir.transport.base
633
# Additional places to query for data.
634
self._fallback_repositories = []
637
return "%s(%s)" % (self.__class__.__name__, self.base)
641
def abort_write_group(self, suppress_errors=False):
642
"""Complete a write group on the decorated repository.
644
Smart methods perform operations in a single step so this API
645
is not really applicable except as a compatibility thunk
646
for older plugins that don't use e.g. the CommitBuilder
649
:param suppress_errors: see Repository.abort_write_group.
652
return self._real_repository.abort_write_group(
653
suppress_errors=suppress_errors)
657
"""Decorate the real repository for now.
659
In the long term a full blown network facility is needed to avoid
660
creating a real repository object locally.
663
return self._real_repository.chk_bytes
665
def commit_write_group(self):
666
"""Complete a write group on the decorated repository.
668
Smart methods perform operations in a single step so this API
669
is not really applicable except as a compatibility thunk
670
for older plugins that don't use e.g. the CommitBuilder
674
return self._real_repository.commit_write_group()
676
def resume_write_group(self, tokens):
678
return self._real_repository.resume_write_group(tokens)
680
def suspend_write_group(self):
682
return self._real_repository.suspend_write_group()
684
def get_missing_parent_inventories(self, check_for_missing_texts=True):
686
return self._real_repository.get_missing_parent_inventories(
687
check_for_missing_texts=check_for_missing_texts)
689
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
691
return self._real_repository.get_rev_id_for_revno(
694
def get_rev_id_for_revno(self, revno, known_pair):
695
"""See Repository.get_rev_id_for_revno."""
696
path = self.bzrdir._path_for_remote_call(self._client)
698
if self._client._medium._is_remote_before((1, 18)):
699
return self._get_rev_id_for_revno_vfs(revno, known_pair)
700
response = self._call(
701
'Repository.get_rev_id_for_revno', path, revno, known_pair)
702
except errors.UnknownSmartMethod:
703
self._client._medium._remember_remote_is_before((1, 18))
704
return self._get_rev_id_for_revno_vfs(revno, known_pair)
705
if response[0] == 'ok':
706
return True, response[1]
707
elif response[0] == 'history-incomplete':
708
known_pair = response[1:3]
709
for fallback in self._fallback_repositories:
710
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
715
# Not found in any fallbacks
716
return False, known_pair
718
raise errors.UnexpectedSmartServerResponse(response)
720
def _ensure_real(self):
721
"""Ensure that there is a _real_repository set.
723
Used before calls to self._real_repository.
725
Note that _ensure_real causes many roundtrips to the server which are
726
not desirable, and prevents the use of smart one-roundtrip RPC's to
727
perform complex operations (such as accessing parent data, streaming
728
revisions etc). Adding calls to _ensure_real should only be done when
729
bringing up new functionality, adding fallbacks for smart methods that
730
require a fallback path, and never to replace an existing smart method
731
invocation. If in doubt chat to the bzr network team.
733
if self._real_repository is None:
734
if 'hpssvfs' in debug.debug_flags:
736
warning('VFS Repository access triggered\n%s',
737
''.join(traceback.format_stack()))
738
self._unstacked_provider.missing_keys.clear()
739
self.bzrdir._ensure_real()
740
self._set_real_repository(
741
self.bzrdir._real_bzrdir.open_repository())
743
def _translate_error(self, err, **context):
744
self.bzrdir._translate_error(err, repository=self, **context)
746
def find_text_key_references(self):
747
"""Find the text key references within the repository.
749
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
750
revision_ids. Each altered file-ids has the exact revision_ids that
751
altered it listed explicitly.
752
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
753
to whether they were referred to by the inventory of the
754
revision_id that they contain. The inventory texts from all present
755
revision ids are assessed to generate this report.
758
return self._real_repository.find_text_key_references()
760
def _generate_text_key_index(self):
761
"""Generate a new text key index for the repository.
763
This is an expensive function that will take considerable time to run.
765
:return: A dict mapping (file_id, revision_id) tuples to a list of
766
parents, also (file_id, revision_id) tuples.
769
return self._real_repository._generate_text_key_index()
771
def _get_revision_graph(self, revision_id):
772
"""Private method for using with old (< 1.2) servers to fallback."""
773
if revision_id is None:
775
elif revision.is_null(revision_id):
778
path = self.bzrdir._path_for_remote_call(self._client)
779
response = self._call_expecting_body(
780
'Repository.get_revision_graph', path, revision_id)
781
response_tuple, response_handler = response
782
if response_tuple[0] != 'ok':
783
raise errors.UnexpectedSmartServerResponse(response_tuple)
784
coded = response_handler.read_body_bytes()
786
# no revisions in this repository!
788
lines = coded.split('\n')
791
d = tuple(line.split())
792
revision_graph[d[0]] = d[1:]
794
return revision_graph
797
"""See Repository._get_sink()."""
798
return RemoteStreamSink(self)
800
def _get_source(self, to_format):
801
"""Return a source for streaming from this repository."""
802
return RemoteStreamSource(self, to_format)
805
def has_revision(self, revision_id):
806
"""True if this repository has a copy of the revision."""
807
# Copy of bzrlib.repository.Repository.has_revision
808
return revision_id in self.has_revisions((revision_id,))
811
def has_revisions(self, revision_ids):
812
"""Probe to find out the presence of multiple revisions.
814
:param revision_ids: An iterable of revision_ids.
815
:return: A set of the revision_ids that were present.
817
# Copy of bzrlib.repository.Repository.has_revisions
818
parent_map = self.get_parent_map(revision_ids)
819
result = set(parent_map)
820
if _mod_revision.NULL_REVISION in revision_ids:
821
result.add(_mod_revision.NULL_REVISION)
824
def has_same_location(self, other):
825
return (self.__class__ is other.__class__ and
826
self.bzrdir.transport.base == other.bzrdir.transport.base)
828
def get_graph(self, other_repository=None):
829
"""Return the graph for this repository format"""
830
parents_provider = self._make_parents_provider(other_repository)
831
return graph.Graph(parents_provider)
833
def gather_stats(self, revid=None, committers=None):
834
"""See Repository.gather_stats()."""
835
path = self.bzrdir._path_for_remote_call(self._client)
836
# revid can be None to indicate no revisions, not just NULL_REVISION
837
if revid is None or revision.is_null(revid):
841
if committers is None or not committers:
842
fmt_committers = 'no'
844
fmt_committers = 'yes'
845
response_tuple, response_handler = self._call_expecting_body(
846
'Repository.gather_stats', path, fmt_revid, fmt_committers)
847
if response_tuple[0] != 'ok':
848
raise errors.UnexpectedSmartServerResponse(response_tuple)
850
body = response_handler.read_body_bytes()
852
for line in body.split('\n'):
855
key, val_text = line.split(':')
856
if key in ('revisions', 'size', 'committers'):
857
result[key] = int(val_text)
858
elif key in ('firstrev', 'latestrev'):
859
values = val_text.split(' ')[1:]
860
result[key] = (float(values[0]), long(values[1]))
864
def find_branches(self, using=False):
865
"""See Repository.find_branches()."""
866
# should be an API call to the server.
868
return self._real_repository.find_branches(using=using)
870
def get_physical_lock_status(self):
871
"""See Repository.get_physical_lock_status()."""
872
# should be an API call to the server.
874
return self._real_repository.get_physical_lock_status()
876
def is_in_write_group(self):
877
"""Return True if there is an open write group.
879
write groups are only applicable locally for the smart server..
881
if self._real_repository:
882
return self._real_repository.is_in_write_group()
885
return self._lock_count >= 1
888
"""See Repository.is_shared()."""
889
path = self.bzrdir._path_for_remote_call(self._client)
890
response = self._call('Repository.is_shared', path)
891
if response[0] not in ('yes', 'no'):
892
raise SmartProtocolError('unexpected response code %s' % (response,))
893
return response[0] == 'yes'
895
def is_write_locked(self):
896
return self._lock_mode == 'w'
899
# wrong eventually - want a local lock cache context
900
if not self._lock_mode:
901
self._lock_mode = 'r'
903
self._unstacked_provider.enable_cache(cache_misses=True)
904
if self._real_repository is not None:
905
self._real_repository.lock_read()
906
for repo in self._fallback_repositories:
909
self._lock_count += 1
911
def _remote_lock_write(self, token):
912
path = self.bzrdir._path_for_remote_call(self._client)
915
err_context = {'token': token}
916
response = self._call('Repository.lock_write', path, token,
918
if response[0] == 'ok':
922
raise errors.UnexpectedSmartServerResponse(response)
924
def lock_write(self, token=None, _skip_rpc=False):
925
if not self._lock_mode:
927
if self._lock_token is not None:
928
if token != self._lock_token:
929
raise errors.TokenMismatch(token, self._lock_token)
930
self._lock_token = token
932
self._lock_token = self._remote_lock_write(token)
933
# if self._lock_token is None, then this is something like packs or
934
# svn where we don't get to lock the repo, or a weave style repository
935
# where we cannot lock it over the wire and attempts to do so will
937
if self._real_repository is not None:
938
self._real_repository.lock_write(token=self._lock_token)
939
if token is not None:
940
self._leave_lock = True
942
self._leave_lock = False
943
self._lock_mode = 'w'
945
cache_misses = self._real_repository is None
946
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
947
for repo in self._fallback_repositories:
948
# Writes don't affect fallback repos
950
elif self._lock_mode == 'r':
951
raise errors.ReadOnlyError(self)
953
self._lock_count += 1
954
return self._lock_token or None
956
def leave_lock_in_place(self):
957
if not self._lock_token:
958
raise NotImplementedError(self.leave_lock_in_place)
959
self._leave_lock = True
961
def dont_leave_lock_in_place(self):
962
if not self._lock_token:
963
raise NotImplementedError(self.dont_leave_lock_in_place)
964
self._leave_lock = False
966
def _set_real_repository(self, repository):
967
"""Set the _real_repository for this repository.
969
:param repository: The repository to fallback to for non-hpss
970
implemented operations.
972
if self._real_repository is not None:
973
# Replacing an already set real repository.
974
# We cannot do this [currently] if the repository is locked -
975
# synchronised state might be lost.
977
raise AssertionError('_real_repository is already set')
978
if isinstance(repository, RemoteRepository):
979
raise AssertionError()
980
self._real_repository = repository
981
# three code paths happen here:
982
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
983
# up stacking. In this case self._fallback_repositories is [], and the
984
# real repo is already setup. Preserve the real repo and
985
# RemoteRepository.add_fallback_repository will avoid adding
987
# 2) new servers, RemoteBranch.open() sets up stacking, and when
988
# ensure_real is triggered from a branch, the real repository to
989
# set already has a matching list with separate instances, but
990
# as they are also RemoteRepositories we don't worry about making the
991
# lists be identical.
992
# 3) new servers, RemoteRepository.ensure_real is triggered before
993
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
994
# and need to populate it.
995
if (self._fallback_repositories and
996
len(self._real_repository._fallback_repositories) !=
997
len(self._fallback_repositories)):
998
if len(self._real_repository._fallback_repositories):
999
raise AssertionError(
1000
"cannot cleanly remove existing _fallback_repositories")
1001
for fb in self._fallback_repositories:
1002
self._real_repository.add_fallback_repository(fb)
1003
if self._lock_mode == 'w':
1004
# if we are already locked, the real repository must be able to
1005
# acquire the lock with our token.
1006
self._real_repository.lock_write(self._lock_token)
1007
elif self._lock_mode == 'r':
1008
self._real_repository.lock_read()
1010
def start_write_group(self):
1011
"""Start a write group on the decorated repository.
1013
Smart methods perform operations in a single step so this API
1014
is not really applicable except as a compatibility thunk
1015
for older plugins that don't use e.g. the CommitBuilder
1019
return self._real_repository.start_write_group()
1021
def _unlock(self, token):
1022
path = self.bzrdir._path_for_remote_call(self._client)
1024
# with no token the remote repository is not persistently locked.
1026
err_context = {'token': token}
1027
response = self._call('Repository.unlock', path, token,
1029
if response == ('ok',):
1032
raise errors.UnexpectedSmartServerResponse(response)
1035
if not self._lock_count:
1036
raise errors.LockNotHeld(self)
1037
self._lock_count -= 1
1038
if self._lock_count > 0:
1040
self._unstacked_provider.disable_cache()
1041
old_mode = self._lock_mode
1042
self._lock_mode = None
1044
# The real repository is responsible at present for raising an
1045
# exception if it's in an unfinished write group. However, it
1046
# normally will *not* actually remove the lock from disk - that's
1047
# done by the server on receiving the Repository.unlock call.
1048
# This is just to let the _real_repository stay up to date.
1049
if self._real_repository is not None:
1050
self._real_repository.unlock()
1052
# The rpc-level lock should be released even if there was a
1053
# problem releasing the vfs-based lock.
1055
# Only write-locked repositories need to make a remote method
1056
# call to perform the unlock.
1057
old_token = self._lock_token
1058
self._lock_token = None
1059
if not self._leave_lock:
1060
self._unlock(old_token)
1061
# Fallbacks are always 'lock_read()' so we don't pay attention to
1063
for repo in self._fallback_repositories:
1066
def break_lock(self):
1067
# should hand off to the network
1069
return self._real_repository.break_lock()
1071
def _get_tarball(self, compression):
1072
"""Return a TemporaryFile containing a repository tarball.
1074
Returns None if the server does not support sending tarballs.
1077
path = self.bzrdir._path_for_remote_call(self._client)
1079
response, protocol = self._call_expecting_body(
1080
'Repository.tarball', path, compression)
1081
except errors.UnknownSmartMethod:
1082
protocol.cancel_read_body()
1084
if response[0] == 'ok':
1085
# Extract the tarball and return it
1086
t = tempfile.NamedTemporaryFile()
1087
# TODO: rpc layer should read directly into it...
1088
t.write(protocol.read_body_bytes())
1091
raise errors.UnexpectedSmartServerResponse(response)
1093
def sprout(self, to_bzrdir, revision_id=None):
1094
# TODO: Option to control what format is created?
1096
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1098
dest_repo.fetch(self, revision_id=revision_id)
1101
### These methods are just thin shims to the VFS object for now.
1103
def revision_tree(self, revision_id):
1105
return self._real_repository.revision_tree(revision_id)
1107
def get_serializer_format(self):
1109
return self._real_repository.get_serializer_format()
1111
def get_commit_builder(self, branch, parents, config, timestamp=None,
1112
timezone=None, committer=None, revprops=None,
1114
# FIXME: It ought to be possible to call this without immediately
1115
# triggering _ensure_real. For now it's the easiest thing to do.
1117
real_repo = self._real_repository
1118
builder = real_repo.get_commit_builder(branch, parents,
1119
config, timestamp=timestamp, timezone=timezone,
1120
committer=committer, revprops=revprops, revision_id=revision_id)
1123
def add_fallback_repository(self, repository):
1124
"""Add a repository to use for looking up data not held locally.
1126
:param repository: A repository.
1128
if not self._format.supports_external_lookups:
1129
raise errors.UnstackableRepositoryFormat(
1130
self._format.network_name(), self.base)
1131
# We need to accumulate additional repositories here, to pass them in
1134
if self.is_locked():
1135
# We will call fallback.unlock() when we transition to the unlocked
1136
# state, so always add a lock here. If a caller passes us a locked
1137
# repository, they are responsible for unlocking it later.
1138
repository.lock_read()
1139
self._fallback_repositories.append(repository)
1140
# If self._real_repository was parameterised already (e.g. because a
1141
# _real_branch had its get_stacked_on_url method called), then the
1142
# repository to be added may already be in the _real_repositories list.
1143
if self._real_repository is not None:
1144
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1145
self._real_repository._fallback_repositories]
1146
if repository.bzrdir.root_transport.base not in fallback_locations:
1147
self._real_repository.add_fallback_repository(repository)
1149
def add_inventory(self, revid, inv, parents):
1151
return self._real_repository.add_inventory(revid, inv, parents)
1153
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1156
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1157
delta, new_revision_id, parents)
1159
def add_revision(self, rev_id, rev, inv=None, config=None):
1161
return self._real_repository.add_revision(
1162
rev_id, rev, inv=inv, config=config)
1165
def get_inventory(self, revision_id):
1167
return self._real_repository.get_inventory(revision_id)
1169
def iter_inventories(self, revision_ids, ordering='unordered'):
1171
return self._real_repository.iter_inventories(revision_ids, ordering)
1174
def get_revision(self, revision_id):
1176
return self._real_repository.get_revision(revision_id)
1178
def get_transaction(self):
1180
return self._real_repository.get_transaction()
1183
def clone(self, a_bzrdir, revision_id=None):
1185
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1187
def make_working_trees(self):
1188
"""See Repository.make_working_trees"""
1190
return self._real_repository.make_working_trees()
1192
def refresh_data(self):
1193
"""Re-read any data needed to to synchronise with disk.
1195
This method is intended to be called after another repository instance
1196
(such as one used by a smart server) has inserted data into the
1197
repository. It may not be called during a write group, but may be
1198
called at any other time.
1200
if self.is_in_write_group():
1201
raise errors.InternalBzrError(
1202
"May not refresh_data while in a write group.")
1203
if self._real_repository is not None:
1204
self._real_repository.refresh_data()
1206
def revision_ids_to_search_result(self, result_set):
1207
"""Convert a set of revision ids to a graph SearchResult."""
1208
result_parents = set()
1209
for parents in self.get_graph().get_parent_map(
1210
result_set).itervalues():
1211
result_parents.update(parents)
1212
included_keys = result_set.intersection(result_parents)
1213
start_keys = result_set.difference(included_keys)
1214
exclude_keys = result_parents.difference(result_set)
1215
result = graph.SearchResult(start_keys, exclude_keys,
1216
len(result_set), result_set)
1220
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1221
"""Return the revision ids that other has that this does not.
1223
These are returned in topological order.
1225
revision_id: only return revision ids included by revision_id.
1227
return repository.InterRepository.get(
1228
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1230
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1232
# No base implementation to use as RemoteRepository is not a subclass
1233
# of Repository; so this is a copy of Repository.fetch().
1234
if fetch_spec is not None and revision_id is not None:
1235
raise AssertionError(
1236
"fetch_spec and revision_id are mutually exclusive.")
1237
if self.is_in_write_group():
1238
raise errors.InternalBzrError(
1239
"May not fetch while in a write group.")
1240
# fast path same-url fetch operations
1241
if self.has_same_location(source) and fetch_spec is None:
1242
# check that last_revision is in 'from' and then return a
1244
if (revision_id is not None and
1245
not revision.is_null(revision_id)):
1246
self.get_revision(revision_id)
1248
# if there is no specific appropriate InterRepository, this will get
1249
# the InterRepository base class, which raises an
1250
# IncompatibleRepositories when asked to fetch.
1251
inter = repository.InterRepository.get(source, self)
1252
return inter.fetch(revision_id=revision_id, pb=pb,
1253
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1255
def create_bundle(self, target, base, fileobj, format=None):
1257
self._real_repository.create_bundle(target, base, fileobj, format)
1260
def get_ancestry(self, revision_id, topo_sorted=True):
1262
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1264
def fileids_altered_by_revision_ids(self, revision_ids):
1266
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1268
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1270
return self._real_repository._get_versioned_file_checker(
1271
revisions, revision_versions_cache)
1273
def iter_files_bytes(self, desired_files):
1274
"""See Repository.iter_file_bytes.
1277
return self._real_repository.iter_files_bytes(desired_files)
1279
def get_parent_map(self, revision_ids):
1280
"""See bzrlib.Graph.get_parent_map()."""
1281
return self._make_parents_provider().get_parent_map(revision_ids)
1283
def _get_parent_map_rpc(self, keys):
1284
"""Helper for get_parent_map that performs the RPC."""
1285
medium = self._client._medium
1286
if medium._is_remote_before((1, 2)):
1287
# We already found out that the server can't understand
1288
# Repository.get_parent_map requests, so just fetch the whole
1291
# Note that this reads the whole graph, when only some keys are
1292
# wanted. On this old server there's no way (?) to get them all
1293
# in one go, and the user probably will have seen a warning about
1294
# the server being old anyhow.
1295
rg = self._get_revision_graph(None)
1296
# There is an API discrepancy between get_parent_map and
1297
# get_revision_graph. Specifically, a "key:()" pair in
1298
# get_revision_graph just means a node has no parents. For
1299
# "get_parent_map" it means the node is a ghost. So fix up the
1300
# graph to correct this.
1301
# https://bugs.launchpad.net/bzr/+bug/214894
1302
# There is one other "bug" which is that ghosts in
1303
# get_revision_graph() are not returned at all. But we won't worry
1304
# about that for now.
1305
for node_id, parent_ids in rg.iteritems():
1306
if parent_ids == ():
1307
rg[node_id] = (NULL_REVISION,)
1308
rg[NULL_REVISION] = ()
1313
raise ValueError('get_parent_map(None) is not valid')
1314
if NULL_REVISION in keys:
1315
keys.discard(NULL_REVISION)
1316
found_parents = {NULL_REVISION:()}
1318
return found_parents
1321
# TODO(Needs analysis): We could assume that the keys being requested
1322
# from get_parent_map are in a breadth first search, so typically they
1323
# will all be depth N from some common parent, and we don't have to
1324
# have the server iterate from the root parent, but rather from the
1325
# keys we're searching; and just tell the server the keyspace we
1326
# already have; but this may be more traffic again.
1328
# Transform self._parents_map into a search request recipe.
1329
# TODO: Manage this incrementally to avoid covering the same path
1330
# repeatedly. (The server will have to on each request, but the less
1331
# work done the better).
1333
# Negative caching notes:
1334
# new server sends missing when a request including the revid
1335
# 'include-missing:' is present in the request.
1336
# missing keys are serialised as missing:X, and we then call
1337
# provider.note_missing(X) for-all X
1338
parents_map = self._unstacked_provider.get_cached_map()
1339
if parents_map is None:
1340
# Repository is not locked, so there's no cache.
1342
# start_set is all the keys in the cache
1343
start_set = set(parents_map)
1344
# result set is all the references to keys in the cache
1345
result_parents = set()
1346
for parents in parents_map.itervalues():
1347
result_parents.update(parents)
1348
stop_keys = result_parents.difference(start_set)
1349
# We don't need to send ghosts back to the server as a position to
1351
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1352
key_count = len(parents_map)
1353
if (NULL_REVISION in result_parents
1354
and NULL_REVISION in self._unstacked_provider.missing_keys):
1355
# If we pruned NULL_REVISION from the stop_keys because it's also
1356
# in our cache of "missing" keys we need to increment our key count
1357
# by 1, because the reconsitituted SearchResult on the server will
1358
# still consider NULL_REVISION to be an included key.
1360
included_keys = start_set.intersection(result_parents)
1361
start_set.difference_update(included_keys)
1362
recipe = ('manual', start_set, stop_keys, key_count)
1363
body = self._serialise_search_recipe(recipe)
1364
path = self.bzrdir._path_for_remote_call(self._client)
1366
if type(key) is not str:
1368
"key %r not a plain string" % (key,))
1369
verb = 'Repository.get_parent_map'
1370
args = (path, 'include-missing:') + tuple(keys)
1372
response = self._call_with_body_bytes_expecting_body(
1374
except errors.UnknownSmartMethod:
1375
# Server does not support this method, so get the whole graph.
1376
# Worse, we have to force a disconnection, because the server now
1377
# doesn't realise it has a body on the wire to consume, so the
1378
# only way to recover is to abandon the connection.
1380
'Server is too old for fast get_parent_map, reconnecting. '
1381
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1383
# To avoid having to disconnect repeatedly, we keep track of the
1384
# fact the server doesn't understand remote methods added in 1.2.
1385
medium._remember_remote_is_before((1, 2))
1386
# Recurse just once and we should use the fallback code.
1387
return self._get_parent_map_rpc(keys)
1388
response_tuple, response_handler = response
1389
if response_tuple[0] not in ['ok']:
1390
response_handler.cancel_read_body()
1391
raise errors.UnexpectedSmartServerResponse(response_tuple)
1392
if response_tuple[0] == 'ok':
1393
coded = bz2.decompress(response_handler.read_body_bytes())
1395
# no revisions found
1397
lines = coded.split('\n')
1400
d = tuple(line.split())
1402
revision_graph[d[0]] = d[1:]
1405
if d[0].startswith('missing:'):
1407
self._unstacked_provider.note_missing_key(revid)
1409
# no parents - so give the Graph result
1411
revision_graph[d[0]] = (NULL_REVISION,)
1412
return revision_graph
1415
def get_signature_text(self, revision_id):
1417
return self._real_repository.get_signature_text(revision_id)
1420
def get_inventory_xml(self, revision_id):
1422
return self._real_repository.get_inventory_xml(revision_id)
1424
def deserialise_inventory(self, revision_id, xml):
1426
return self._real_repository.deserialise_inventory(revision_id, xml)
1428
def reconcile(self, other=None, thorough=False):
1430
return self._real_repository.reconcile(other=other, thorough=thorough)
1432
def all_revision_ids(self):
1434
return self._real_repository.all_revision_ids()
1437
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1439
return self._real_repository.get_deltas_for_revisions(revisions,
1440
specific_fileids=specific_fileids)
1443
def get_revision_delta(self, revision_id, specific_fileids=None):
1445
return self._real_repository.get_revision_delta(revision_id,
1446
specific_fileids=specific_fileids)
1449
def revision_trees(self, revision_ids):
1451
return self._real_repository.revision_trees(revision_ids)
1454
def get_revision_reconcile(self, revision_id):
1456
return self._real_repository.get_revision_reconcile(revision_id)
1459
def check(self, revision_ids=None):
1461
return self._real_repository.check(revision_ids=revision_ids)
1463
def copy_content_into(self, destination, revision_id=None):
1465
return self._real_repository.copy_content_into(
1466
destination, revision_id=revision_id)
1468
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1469
# get a tarball of the remote repository, and copy from that into the
1471
from bzrlib import osutils
1473
# TODO: Maybe a progress bar while streaming the tarball?
1474
note("Copying repository content as tarball...")
1475
tar_file = self._get_tarball('bz2')
1476
if tar_file is None:
1478
destination = to_bzrdir.create_repository()
1480
tar = tarfile.open('repository', fileobj=tar_file,
1482
tmpdir = osutils.mkdtemp()
1484
_extract_tar(tar, tmpdir)
1485
tmp_bzrdir = BzrDir.open(tmpdir)
1486
tmp_repo = tmp_bzrdir.open_repository()
1487
tmp_repo.copy_content_into(destination, revision_id)
1489
osutils.rmtree(tmpdir)
1493
# TODO: Suggestion from john: using external tar is much faster than
1494
# python's tarfile library, but it may not work on windows.
1497
def inventories(self):
1498
"""Decorate the real repository for now.
1500
In the long term a full blown network facility is needed to
1501
avoid creating a real repository object locally.
1504
return self._real_repository.inventories
1507
def pack(self, hint=None):
1508
"""Compress the data within the repository.
1510
This is not currently implemented within the smart server.
1513
return self._real_repository.pack(hint=hint)
1516
def revisions(self):
1517
"""Decorate the real repository for now.
1519
In the short term this should become a real object to intercept graph
1522
In the long term a full blown network facility is needed.
1525
return self._real_repository.revisions
1527
def set_make_working_trees(self, new_value):
1529
new_value_str = "True"
1531
new_value_str = "False"
1532
path = self.bzrdir._path_for_remote_call(self._client)
1534
response = self._call(
1535
'Repository.set_make_working_trees', path, new_value_str)
1536
except errors.UnknownSmartMethod:
1538
self._real_repository.set_make_working_trees(new_value)
1540
if response[0] != 'ok':
1541
raise errors.UnexpectedSmartServerResponse(response)
1544
def signatures(self):
1545
"""Decorate the real repository for now.
1547
In the long term a full blown network facility is needed to avoid
1548
creating a real repository object locally.
1551
return self._real_repository.signatures
1554
def sign_revision(self, revision_id, gpg_strategy):
1556
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1560
"""Decorate the real repository for now.
1562
In the long term a full blown network facility is needed to avoid
1563
creating a real repository object locally.
1566
return self._real_repository.texts
1569
def get_revisions(self, revision_ids):
1571
return self._real_repository.get_revisions(revision_ids)
1573
def supports_rich_root(self):
1574
return self._format.rich_root_data
1576
def iter_reverse_revision_history(self, revision_id):
1578
return self._real_repository.iter_reverse_revision_history(revision_id)
1581
def _serializer(self):
1582
return self._format._serializer
1584
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1586
return self._real_repository.store_revision_signature(
1587
gpg_strategy, plaintext, revision_id)
1589
def add_signature_text(self, revision_id, signature):
1591
return self._real_repository.add_signature_text(revision_id, signature)
1593
def has_signature_for_revision_id(self, revision_id):
1595
return self._real_repository.has_signature_for_revision_id(revision_id)
1597
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1599
return self._real_repository.item_keys_introduced_by(revision_ids,
1600
_files_pb=_files_pb)
1602
def revision_graph_can_have_wrong_parents(self):
1603
# The answer depends on the remote repo format.
1605
return self._real_repository.revision_graph_can_have_wrong_parents()
1607
def _find_inconsistent_revision_parents(self):
1609
return self._real_repository._find_inconsistent_revision_parents()
1611
def _check_for_inconsistent_revision_parents(self):
1613
return self._real_repository._check_for_inconsistent_revision_parents()
1615
def _make_parents_provider(self, other=None):
1616
providers = [self._unstacked_provider]
1617
if other is not None:
1618
providers.insert(0, other)
1619
providers.extend(r._make_parents_provider() for r in
1620
self._fallback_repositories)
1621
return graph.StackedParentsProvider(providers)
1623
def _serialise_search_recipe(self, recipe):
1624
"""Serialise a graph search recipe.
1626
:param recipe: A search recipe (start, stop, count).
1627
:return: Serialised bytes.
1629
start_keys = ' '.join(recipe[1])
1630
stop_keys = ' '.join(recipe[2])
1631
count = str(recipe[3])
1632
return '\n'.join((start_keys, stop_keys, count))
1634
def _serialise_search_result(self, search_result):
1635
if isinstance(search_result, graph.PendingAncestryResult):
1636
parts = ['ancestry-of']
1637
parts.extend(search_result.heads)
1639
recipe = search_result.get_recipe()
1640
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1641
return '\n'.join(parts)
1644
path = self.bzrdir._path_for_remote_call(self._client)
1646
response = self._call('PackRepository.autopack', path)
1647
except errors.UnknownSmartMethod:
1649
self._real_repository._pack_collection.autopack()
1652
if response[0] != 'ok':
1653
raise errors.UnexpectedSmartServerResponse(response)
1656
class RemoteStreamSink(repository.StreamSink):
1658
def __init__(self, target_repo):
1659
repository.StreamSink.__init__(self, target_repo)
1661
def _insert_real(self, stream, src_format, resume_tokens):
1662
self.target_repo._ensure_real()
1663
sink = self.target_repo._real_repository._get_sink()
1664
result = sink.insert_stream(stream, src_format, resume_tokens)
1666
self.target_repo.autopack()
1669
def insert_stream(self, stream, src_format, resume_tokens):
1670
target = self.target_repo
1671
target._unstacked_provider.missing_keys.clear()
1672
candidate_calls = [('Repository.insert_stream_1.18', (1, 18))]
1673
if target._lock_token:
1674
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1675
lock_args = (target._lock_token or '',)
1677
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1679
client = target._client
1680
medium = client._medium
1681
path = target.bzrdir._path_for_remote_call(client)
1683
for verb, required_version in candidate_calls:
1684
if medium._is_remote_before(required_version):
1687
# We've already done the probing (and set _is_remote_before) on
1688
# a previous insert.
1691
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1693
response = client.call_with_body_stream(
1694
(verb, path, '') + lock_args, byte_stream)
1695
except errors.UnknownSmartMethod:
1696
medium._remember_remote_is_before(required_version)
1702
return self._insert_real(stream, src_format, resume_tokens)
1703
self._last_inv_record = None
1704
self._last_substream = None
1705
if required_version < (1, 18):
1706
# Remote side doesn't support inventory deltas.
1707
stream = self._stop_stream_if_inventory_delta(stream)
1708
byte_stream = smart_repo._stream_to_byte_stream(
1710
resume_tokens = ' '.join(resume_tokens)
1711
response = client.call_with_body_stream(
1712
(verb, path, resume_tokens) + lock_args, byte_stream)
1713
if response[0][0] not in ('ok', 'missing-basis'):
1714
raise errors.UnexpectedSmartServerResponse(response)
1715
if self._last_inv_record is not None:
1716
# The stream included an inventory-delta record, but the remote
1717
# side isn't new enough to support them. So we need to send the
1718
# rest of the stream via VFS.
1719
return self._resume_stream_with_vfs(response, src_format)
1720
if response[0][0] == 'missing-basis':
1721
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1722
resume_tokens = tokens
1723
return resume_tokens, set(missing_keys)
1725
self.target_repo.refresh_data()
1728
def _resume_stream_with_vfs(self, response, src_format):
1729
"""Resume sending a stream via VFS, first resending the record and
1730
substream that couldn't be sent via an insert_stream verb.
1732
if response[0][0] == 'missing-basis':
1733
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1734
# Ignore missing_keys, we haven't finished inserting yet
1737
def resume_substream():
1738
# First yield the record we stopped at.
1739
yield self._last_inv_record
1740
self._last_inv_record = None
1741
# Then yield the rest of the substream that was interrupted.
1742
for record in self._last_substream:
1744
self._last_substream = None
1745
def resume_stream():
1746
# Finish sending the interrupted substream
1747
yield ('inventories', resume_substream())
1748
# Then simply continue sending the rest of the stream.
1749
for substream_kind, substream in self._last_stream:
1750
yield substream_kind, substream
1751
return self._insert_real(resume_stream(), src_format, tokens)
1753
def _stop_stream_if_inventory_delta(self, stream):
1754
"""Normally this just lets the original stream pass-through unchanged.
1756
However if any 'inventories' substream includes an inventory-delta
1757
record it will stop streaming, and store the interrupted record,
1758
substream and stream in self._last_inv_record, self._last_substream and
1759
self._last_stream so that the stream can be resumed by
1760
_resume_stream_with_vfs.
1762
def filter_inv_substream(inv_substream):
1763
substream_iter = iter(inv_substream)
1764
for record in substream_iter:
1765
if record.storage_kind == 'inventory-delta':
1766
self._last_inv_record = record
1767
self._last_substream = substream_iter
1772
stream_iter = iter(stream)
1773
for substream_kind, substream in stream_iter:
1774
if substream_kind == 'inventories':
1775
yield substream_kind, filter_inv_substream(substream)
1776
if self._last_inv_record is not None:
1777
self._last_stream = stream_iter
1780
yield substream_kind, substream
1783
class RemoteStreamSource(repository.StreamSource):
1784
"""Stream data from a remote server."""
1786
def get_stream(self, search):
1787
if (self.from_repository._fallback_repositories and
1788
self.to_format._fetch_order == 'topological'):
1789
return self._real_stream(self.from_repository, search)
1790
return self.missing_parents_chain(search, [self.from_repository] +
1791
self.from_repository._fallback_repositories)
1793
def get_stream_for_missing_keys(self, missing_keys):
1794
self.from_repository._ensure_real()
1795
real_repo = self.from_repository._real_repository
1796
real_source = real_repo._get_source(self.to_format)
1797
return real_source.get_stream_for_missing_keys(missing_keys)
1799
def _real_stream(self, repo, search):
1800
"""Get a stream for search from repo.
1802
This never called RemoteStreamSource.get_stream, and is a heler
1803
for RemoteStreamSource._get_stream to allow getting a stream
1804
reliably whether fallback back because of old servers or trying
1805
to stream from a non-RemoteRepository (which the stacked support
1808
source = repo._get_source(self.to_format)
1809
if isinstance(source, RemoteStreamSource):
1810
return repository.StreamSource.get_stream(source, search)
1811
return source.get_stream(search)
1813
def _get_stream(self, repo, search):
1814
"""Core worker to get a stream from repo for search.
1816
This is used by both get_stream and the stacking support logic. It
1817
deliberately gets a stream for repo which does not need to be
1818
self.from_repository. In the event that repo is not Remote, or
1819
cannot do a smart stream, a fallback is made to the generic
1820
repository._get_stream() interface, via self._real_stream.
1822
In the event of stacking, streams from _get_stream will not
1823
contain all the data for search - this is normal (see get_stream).
1825
:param repo: A repository.
1826
:param search: A search.
1828
# Fallbacks may be non-smart
1829
if not isinstance(repo, RemoteRepository):
1830
return self._real_stream(repo, search)
1831
client = repo._client
1832
medium = client._medium
1833
if medium._is_remote_before((1, 13)):
1834
# streaming was added in 1.13
1835
return self._real_stream(repo, search)
1836
path = repo.bzrdir._path_for_remote_call(client)
1838
search_bytes = repo._serialise_search_result(search)
1839
response = repo._call_with_body_bytes_expecting_body(
1840
'Repository.get_stream',
1841
(path, self.to_format.network_name()), search_bytes)
1842
response_tuple, response_handler = response
1843
except errors.UnknownSmartMethod:
1844
medium._remember_remote_is_before((1,13))
1845
return self._real_stream(repo, search)
1846
if response_tuple[0] != 'ok':
1847
raise errors.UnexpectedSmartServerResponse(response_tuple)
1848
byte_stream = response_handler.read_streamed_body()
1849
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1850
if src_format.network_name() != repo._format.network_name():
1851
raise AssertionError(
1852
"Mismatched RemoteRepository and stream src %r, %r" % (
1853
src_format.network_name(), repo._format.network_name()))
1856
def missing_parents_chain(self, search, sources):
1857
"""Chain multiple streams together to handle stacking.
1859
:param search: The overall search to satisfy with streams.
1860
:param sources: A list of Repository objects to query.
1862
self.serialiser = self.to_format._serializer
1863
self.seen_revs = set()
1864
self.referenced_revs = set()
1865
# If there are heads in the search, or the key count is > 0, we are not
1867
while not search.is_empty() and len(sources) > 1:
1868
source = sources.pop(0)
1869
stream = self._get_stream(source, search)
1870
for kind, substream in stream:
1871
if kind != 'revisions':
1872
yield kind, substream
1874
yield kind, self.missing_parents_rev_handler(substream)
1875
search = search.refine(self.seen_revs, self.referenced_revs)
1876
self.seen_revs = set()
1877
self.referenced_revs = set()
1878
if not search.is_empty():
1879
for kind, stream in self._get_stream(sources[0], search):
1882
def missing_parents_rev_handler(self, substream):
1883
for content in substream:
1884
revision_bytes = content.get_bytes_as('fulltext')
1885
revision = self.serialiser.read_revision_from_string(revision_bytes)
1886
self.seen_revs.add(content.key[-1])
1887
self.referenced_revs.update(revision.parent_ids)
1891
class RemoteBranchLockableFiles(LockableFiles):
1892
"""A 'LockableFiles' implementation that talks to a smart server.
1894
This is not a public interface class.
1897
def __init__(self, bzrdir, _client):
1898
self.bzrdir = bzrdir
1899
self._client = _client
1900
self._need_find_modes = True
1901
LockableFiles.__init__(
1902
self, bzrdir.get_branch_transport(None),
1903
'lock', lockdir.LockDir)
1905
def _find_modes(self):
1906
# RemoteBranches don't let the client set the mode of control files.
1907
self._dir_mode = None
1908
self._file_mode = None
1911
class RemoteBranchFormat(branch.BranchFormat):
1913
def __init__(self, network_name=None):
1914
super(RemoteBranchFormat, self).__init__()
1915
self._matchingbzrdir = RemoteBzrDirFormat()
1916
self._matchingbzrdir.set_branch_format(self)
1917
self._custom_format = None
1918
self._network_name = network_name
1920
def __eq__(self, other):
1921
return (isinstance(other, RemoteBranchFormat) and
1922
self.__dict__ == other.__dict__)
1924
def _ensure_real(self):
1925
if self._custom_format is None:
1926
self._custom_format = branch.network_format_registry.get(
1929
def get_format_description(self):
1930
return 'Remote BZR Branch'
1932
def network_name(self):
1933
return self._network_name
1935
def open(self, a_bzrdir, ignore_fallbacks=False):
1936
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
1938
def _vfs_initialize(self, a_bzrdir):
1939
# Initialisation when using a local bzrdir object, or a non-vfs init
1940
# method is not available on the server.
1941
# self._custom_format is always set - the start of initialize ensures
1943
if isinstance(a_bzrdir, RemoteBzrDir):
1944
a_bzrdir._ensure_real()
1945
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1947
# We assume the bzrdir is parameterised; it may not be.
1948
result = self._custom_format.initialize(a_bzrdir)
1949
if (isinstance(a_bzrdir, RemoteBzrDir) and
1950
not isinstance(result, RemoteBranch)):
1951
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1954
def initialize(self, a_bzrdir):
1955
# 1) get the network name to use.
1956
if self._custom_format:
1957
network_name = self._custom_format.network_name()
1959
# Select the current bzrlib default and ask for that.
1960
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1961
reference_format = reference_bzrdir_format.get_branch_format()
1962
self._custom_format = reference_format
1963
network_name = reference_format.network_name()
1964
# Being asked to create on a non RemoteBzrDir:
1965
if not isinstance(a_bzrdir, RemoteBzrDir):
1966
return self._vfs_initialize(a_bzrdir)
1967
medium = a_bzrdir._client._medium
1968
if medium._is_remote_before((1, 13)):
1969
return self._vfs_initialize(a_bzrdir)
1970
# Creating on a remote bzr dir.
1971
# 2) try direct creation via RPC
1972
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1973
verb = 'BzrDir.create_branch'
1975
response = a_bzrdir._call(verb, path, network_name)
1976
except errors.UnknownSmartMethod:
1977
# Fallback - use vfs methods
1978
medium._remember_remote_is_before((1, 13))
1979
return self._vfs_initialize(a_bzrdir)
1980
if response[0] != 'ok':
1981
raise errors.UnexpectedSmartServerResponse(response)
1982
# Turn the response into a RemoteRepository object.
1983
format = RemoteBranchFormat(network_name=response[1])
1984
repo_format = response_tuple_to_repo_format(response[3:])
1985
if response[2] == '':
1986
repo_bzrdir = a_bzrdir
1988
repo_bzrdir = RemoteBzrDir(
1989
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1991
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1992
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1993
format=format, setup_stacking=False)
1994
# XXX: We know this is a new branch, so it must have revno 0, revid
1995
# NULL_REVISION. Creating the branch locked would make this be unable
1996
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1997
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1998
return remote_branch
2000
def make_tags(self, branch):
2002
return self._custom_format.make_tags(branch)
2004
def supports_tags(self):
2005
# Remote branches might support tags, but we won't know until we
2006
# access the real remote branch.
2008
return self._custom_format.supports_tags()
2010
def supports_stacking(self):
2012
return self._custom_format.supports_stacking()
2014
def supports_set_append_revisions_only(self):
2016
return self._custom_format.supports_set_append_revisions_only()
2019
class RemoteBranch(branch.Branch, _RpcHelper):
2020
"""Branch stored on a server accessed by HPSS RPC.
2022
At the moment most operations are mapped down to simple file operations.
2025
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2026
_client=None, format=None, setup_stacking=True):
2027
"""Create a RemoteBranch instance.
2029
:param real_branch: An optional local implementation of the branch
2030
format, usually accessing the data via the VFS.
2031
:param _client: Private parameter for testing.
2032
:param format: A RemoteBranchFormat object, None to create one
2033
automatically. If supplied it should have a network_name already
2035
:param setup_stacking: If True make an RPC call to determine the
2036
stacked (or not) status of the branch. If False assume the branch
2039
# We intentionally don't call the parent class's __init__, because it
2040
# will try to assign to self.tags, which is a property in this subclass.
2041
# And the parent's __init__ doesn't do much anyway.
2042
self.bzrdir = remote_bzrdir
2043
if _client is not None:
2044
self._client = _client
2046
self._client = remote_bzrdir._client
2047
self.repository = remote_repository
2048
if real_branch is not None:
2049
self._real_branch = real_branch
2050
# Give the remote repository the matching real repo.
2051
real_repo = self._real_branch.repository
2052
if isinstance(real_repo, RemoteRepository):
2053
real_repo._ensure_real()
2054
real_repo = real_repo._real_repository
2055
self.repository._set_real_repository(real_repo)
2056
# Give the branch the remote repository to let fast-pathing happen.
2057
self._real_branch.repository = self.repository
2059
self._real_branch = None
2060
# Fill out expected attributes of branch for bzrlib API users.
2061
self._clear_cached_state()
2062
self.base = self.bzrdir.root_transport.base
2063
self._control_files = None
2064
self._lock_mode = None
2065
self._lock_token = None
2066
self._repo_lock_token = None
2067
self._lock_count = 0
2068
self._leave_lock = False
2069
# Setup a format: note that we cannot call _ensure_real until all the
2070
# attributes above are set: This code cannot be moved higher up in this
2073
self._format = RemoteBranchFormat()
2074
if real_branch is not None:
2075
self._format._network_name = \
2076
self._real_branch._format.network_name()
2078
self._format = format
2079
if not self._format._network_name:
2080
# Did not get from open_branchV2 - old server.
2082
self._format._network_name = \
2083
self._real_branch._format.network_name()
2084
self.tags = self._format.make_tags(self)
2085
# The base class init is not called, so we duplicate this:
2086
hooks = branch.Branch.hooks['open']
2089
self._is_stacked = False
2091
self._setup_stacking()
2093
def _setup_stacking(self):
2094
# configure stacking into the remote repository, by reading it from
2097
fallback_url = self.get_stacked_on_url()
2098
except (errors.NotStacked, errors.UnstackableBranchFormat,
2099
errors.UnstackableRepositoryFormat), e:
2101
self._is_stacked = True
2102
self._activate_fallback_location(fallback_url)
2104
def _get_config(self):
2105
return RemoteBranchConfig(self)
2107
def _get_real_transport(self):
2108
# if we try vfs access, return the real branch's vfs transport
2110
return self._real_branch._transport
2112
_transport = property(_get_real_transport)
2115
return "%s(%s)" % (self.__class__.__name__, self.base)
2119
def _ensure_real(self):
2120
"""Ensure that there is a _real_branch set.
2122
Used before calls to self._real_branch.
2124
if self._real_branch is None:
2125
if not vfs.vfs_enabled():
2126
raise AssertionError('smart server vfs must be enabled '
2127
'to use vfs implementation')
2128
self.bzrdir._ensure_real()
2129
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
2130
if self.repository._real_repository is None:
2131
# Give the remote repository the matching real repo.
2132
real_repo = self._real_branch.repository
2133
if isinstance(real_repo, RemoteRepository):
2134
real_repo._ensure_real()
2135
real_repo = real_repo._real_repository
2136
self.repository._set_real_repository(real_repo)
2137
# Give the real branch the remote repository to let fast-pathing
2139
self._real_branch.repository = self.repository
2140
if self._lock_mode == 'r':
2141
self._real_branch.lock_read()
2142
elif self._lock_mode == 'w':
2143
self._real_branch.lock_write(token=self._lock_token)
2145
def _translate_error(self, err, **context):
2146
self.repository._translate_error(err, branch=self, **context)
2148
def _clear_cached_state(self):
2149
super(RemoteBranch, self)._clear_cached_state()
2150
if self._real_branch is not None:
2151
self._real_branch._clear_cached_state()
2153
def _clear_cached_state_of_remote_branch_only(self):
2154
"""Like _clear_cached_state, but doesn't clear the cache of
2157
This is useful when falling back to calling a method of
2158
self._real_branch that changes state. In that case the underlying
2159
branch changes, so we need to invalidate this RemoteBranch's cache of
2160
it. However, there's no need to invalidate the _real_branch's cache
2161
too, in fact doing so might harm performance.
2163
super(RemoteBranch, self)._clear_cached_state()
2166
def control_files(self):
2167
# Defer actually creating RemoteBranchLockableFiles until its needed,
2168
# because it triggers an _ensure_real that we otherwise might not need.
2169
if self._control_files is None:
2170
self._control_files = RemoteBranchLockableFiles(
2171
self.bzrdir, self._client)
2172
return self._control_files
2174
def _get_checkout_format(self):
2176
return self._real_branch._get_checkout_format()
2178
def get_physical_lock_status(self):
2179
"""See Branch.get_physical_lock_status()."""
2180
# should be an API call to the server, as branches must be lockable.
2182
return self._real_branch.get_physical_lock_status()
2184
def get_stacked_on_url(self):
2185
"""Get the URL this branch is stacked against.
2187
:raises NotStacked: If the branch is not stacked.
2188
:raises UnstackableBranchFormat: If the branch does not support
2190
:raises UnstackableRepositoryFormat: If the repository does not support
2194
# there may not be a repository yet, so we can't use
2195
# self._translate_error, so we can't use self._call either.
2196
response = self._client.call('Branch.get_stacked_on_url',
2197
self._remote_path())
2198
except errors.ErrorFromSmartServer, err:
2199
# there may not be a repository yet, so we can't call through
2200
# its _translate_error
2201
_translate_error(err, branch=self)
2202
except errors.UnknownSmartMethod, err:
2204
return self._real_branch.get_stacked_on_url()
2205
if response[0] != 'ok':
2206
raise errors.UnexpectedSmartServerResponse(response)
2209
def set_stacked_on_url(self, url):
2210
branch.Branch.set_stacked_on_url(self, url)
2212
self._is_stacked = False
2214
self._is_stacked = True
2216
def _vfs_get_tags_bytes(self):
2218
return self._real_branch._get_tags_bytes()
2220
def _get_tags_bytes(self):
2221
medium = self._client._medium
2222
if medium._is_remote_before((1, 13)):
2223
return self._vfs_get_tags_bytes()
2225
response = self._call('Branch.get_tags_bytes', self._remote_path())
2226
except errors.UnknownSmartMethod:
2227
medium._remember_remote_is_before((1, 13))
2228
return self._vfs_get_tags_bytes()
2231
def lock_read(self):
2232
self.repository.lock_read()
2233
if not self._lock_mode:
2234
self._lock_mode = 'r'
2235
self._lock_count = 1
2236
if self._real_branch is not None:
2237
self._real_branch.lock_read()
2239
self._lock_count += 1
2241
def _remote_lock_write(self, token):
2243
branch_token = repo_token = ''
2245
branch_token = token
2246
repo_token = self.repository.lock_write()
2247
self.repository.unlock()
2248
err_context = {'token': token}
2249
response = self._call(
2250
'Branch.lock_write', self._remote_path(), branch_token,
2251
repo_token or '', **err_context)
2252
if response[0] != 'ok':
2253
raise errors.UnexpectedSmartServerResponse(response)
2254
ok, branch_token, repo_token = response
2255
return branch_token, repo_token
2257
def lock_write(self, token=None):
2258
if not self._lock_mode:
2259
# Lock the branch and repo in one remote call.
2260
remote_tokens = self._remote_lock_write(token)
2261
self._lock_token, self._repo_lock_token = remote_tokens
2262
if not self._lock_token:
2263
raise SmartProtocolError('Remote server did not return a token!')
2264
# Tell the self.repository object that it is locked.
2265
self.repository.lock_write(
2266
self._repo_lock_token, _skip_rpc=True)
2268
if self._real_branch is not None:
2269
self._real_branch.lock_write(token=self._lock_token)
2270
if token is not None:
2271
self._leave_lock = True
2273
self._leave_lock = False
2274
self._lock_mode = 'w'
2275
self._lock_count = 1
2276
elif self._lock_mode == 'r':
2277
raise errors.ReadOnlyTransaction
2279
if token is not None:
2280
# A token was given to lock_write, and we're relocking, so
2281
# check that the given token actually matches the one we
2283
if token != self._lock_token:
2284
raise errors.TokenMismatch(token, self._lock_token)
2285
self._lock_count += 1
2286
# Re-lock the repository too.
2287
self.repository.lock_write(self._repo_lock_token)
2288
return self._lock_token or None
2290
def _set_tags_bytes(self, bytes):
2292
return self._real_branch._set_tags_bytes(bytes)
2294
def _unlock(self, branch_token, repo_token):
2295
err_context = {'token': str((branch_token, repo_token))}
2296
response = self._call(
2297
'Branch.unlock', self._remote_path(), branch_token,
2298
repo_token or '', **err_context)
2299
if response == ('ok',):
2301
raise errors.UnexpectedSmartServerResponse(response)
2305
self._lock_count -= 1
2306
if not self._lock_count:
2307
self._clear_cached_state()
2308
mode = self._lock_mode
2309
self._lock_mode = None
2310
if self._real_branch is not None:
2311
if (not self._leave_lock and mode == 'w' and
2312
self._repo_lock_token):
2313
# If this RemoteBranch will remove the physical lock
2314
# for the repository, make sure the _real_branch
2315
# doesn't do it first. (Because the _real_branch's
2316
# repository is set to be the RemoteRepository.)
2317
self._real_branch.repository.leave_lock_in_place()
2318
self._real_branch.unlock()
2320
# Only write-locked branched need to make a remote method
2321
# call to perform the unlock.
2323
if not self._lock_token:
2324
raise AssertionError('Locked, but no token!')
2325
branch_token = self._lock_token
2326
repo_token = self._repo_lock_token
2327
self._lock_token = None
2328
self._repo_lock_token = None
2329
if not self._leave_lock:
2330
self._unlock(branch_token, repo_token)
2332
self.repository.unlock()
2334
def break_lock(self):
2336
return self._real_branch.break_lock()
2338
def leave_lock_in_place(self):
2339
if not self._lock_token:
2340
raise NotImplementedError(self.leave_lock_in_place)
2341
self._leave_lock = True
2343
def dont_leave_lock_in_place(self):
2344
if not self._lock_token:
2345
raise NotImplementedError(self.dont_leave_lock_in_place)
2346
self._leave_lock = False
2348
def get_rev_id(self, revno, history=None):
2350
return _mod_revision.NULL_REVISION
2351
last_revision_info = self.last_revision_info()
2352
ok, result = self.repository.get_rev_id_for_revno(
2353
revno, last_revision_info)
2356
missing_parent = result[1]
2357
# Either the revision named by the server is missing, or its parent
2358
# is. Call get_parent_map to determine which, so that we report a
2360
parent_map = self.repository.get_parent_map([missing_parent])
2361
if missing_parent in parent_map:
2362
missing_parent = parent_map[missing_parent]
2363
raise errors.RevisionNotPresent(missing_parent, self.repository)
2365
def _last_revision_info(self):
2366
response = self._call('Branch.last_revision_info', self._remote_path())
2367
if response[0] != 'ok':
2368
raise SmartProtocolError('unexpected response code %s' % (response,))
2369
revno = int(response[1])
2370
last_revision = response[2]
2371
return (revno, last_revision)
2373
def _gen_revision_history(self):
2374
"""See Branch._gen_revision_history()."""
2375
if self._is_stacked:
2377
return self._real_branch._gen_revision_history()
2378
response_tuple, response_handler = self._call_expecting_body(
2379
'Branch.revision_history', self._remote_path())
2380
if response_tuple[0] != 'ok':
2381
raise errors.UnexpectedSmartServerResponse(response_tuple)
2382
result = response_handler.read_body_bytes().split('\x00')
2387
def _remote_path(self):
2388
return self.bzrdir._path_for_remote_call(self._client)
2390
def _set_last_revision_descendant(self, revision_id, other_branch,
2391
allow_diverged=False, allow_overwrite_descendant=False):
2392
# This performs additional work to meet the hook contract; while its
2393
# undesirable, we have to synthesise the revno to call the hook, and
2394
# not calling the hook is worse as it means changes can't be prevented.
2395
# Having calculated this though, we can't just call into
2396
# set_last_revision_info as a simple call, because there is a set_rh
2397
# hook that some folk may still be using.
2398
old_revno, old_revid = self.last_revision_info()
2399
history = self._lefthand_history(revision_id)
2400
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2401
err_context = {'other_branch': other_branch}
2402
response = self._call('Branch.set_last_revision_ex',
2403
self._remote_path(), self._lock_token, self._repo_lock_token,
2404
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2406
self._clear_cached_state()
2407
if len(response) != 3 and response[0] != 'ok':
2408
raise errors.UnexpectedSmartServerResponse(response)
2409
new_revno, new_revision_id = response[1:]
2410
self._last_revision_info_cache = new_revno, new_revision_id
2411
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2412
if self._real_branch is not None:
2413
cache = new_revno, new_revision_id
2414
self._real_branch._last_revision_info_cache = cache
2416
def _set_last_revision(self, revision_id):
2417
old_revno, old_revid = self.last_revision_info()
2418
# This performs additional work to meet the hook contract; while its
2419
# undesirable, we have to synthesise the revno to call the hook, and
2420
# not calling the hook is worse as it means changes can't be prevented.
2421
# Having calculated this though, we can't just call into
2422
# set_last_revision_info as a simple call, because there is a set_rh
2423
# hook that some folk may still be using.
2424
history = self._lefthand_history(revision_id)
2425
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2426
self._clear_cached_state()
2427
response = self._call('Branch.set_last_revision',
2428
self._remote_path(), self._lock_token, self._repo_lock_token,
2430
if response != ('ok',):
2431
raise errors.UnexpectedSmartServerResponse(response)
2432
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2435
def set_revision_history(self, rev_history):
2436
# Send just the tip revision of the history; the server will generate
2437
# the full history from that. If the revision doesn't exist in this
2438
# branch, NoSuchRevision will be raised.
2439
if rev_history == []:
2442
rev_id = rev_history[-1]
2443
self._set_last_revision(rev_id)
2444
for hook in branch.Branch.hooks['set_rh']:
2445
hook(self, rev_history)
2446
self._cache_revision_history(rev_history)
2448
def _get_parent_location(self):
2449
medium = self._client._medium
2450
if medium._is_remote_before((1, 13)):
2451
return self._vfs_get_parent_location()
2453
response = self._call('Branch.get_parent', self._remote_path())
2454
except errors.UnknownSmartMethod:
2455
medium._remember_remote_is_before((1, 13))
2456
return self._vfs_get_parent_location()
2457
if len(response) != 1:
2458
raise errors.UnexpectedSmartServerResponse(response)
2459
parent_location = response[0]
2460
if parent_location == '':
2462
return parent_location
2464
def _vfs_get_parent_location(self):
2466
return self._real_branch._get_parent_location()
2468
def _set_parent_location(self, url):
2469
medium = self._client._medium
2470
if medium._is_remote_before((1, 15)):
2471
return self._vfs_set_parent_location(url)
2473
call_url = url or ''
2474
if type(call_url) is not str:
2475
raise AssertionError('url must be a str or None (%s)' % url)
2476
response = self._call('Branch.set_parent_location',
2477
self._remote_path(), self._lock_token, self._repo_lock_token,
2479
except errors.UnknownSmartMethod:
2480
medium._remember_remote_is_before((1, 15))
2481
return self._vfs_set_parent_location(url)
2483
raise errors.UnexpectedSmartServerResponse(response)
2485
def _vfs_set_parent_location(self, url):
2487
return self._real_branch._set_parent_location(url)
2490
def pull(self, source, overwrite=False, stop_revision=None,
2492
self._clear_cached_state_of_remote_branch_only()
2494
return self._real_branch.pull(
2495
source, overwrite=overwrite, stop_revision=stop_revision,
2496
_override_hook_target=self, **kwargs)
2499
def push(self, target, overwrite=False, stop_revision=None):
2501
return self._real_branch.push(
2502
target, overwrite=overwrite, stop_revision=stop_revision,
2503
_override_hook_source_branch=self)
2505
def is_locked(self):
2506
return self._lock_count >= 1
2509
def revision_id_to_revno(self, revision_id):
2511
return self._real_branch.revision_id_to_revno(revision_id)
2514
def set_last_revision_info(self, revno, revision_id):
2515
# XXX: These should be returned by the set_last_revision_info verb
2516
old_revno, old_revid = self.last_revision_info()
2517
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2518
revision_id = ensure_null(revision_id)
2520
response = self._call('Branch.set_last_revision_info',
2521
self._remote_path(), self._lock_token, self._repo_lock_token,
2522
str(revno), revision_id)
2523
except errors.UnknownSmartMethod:
2525
self._clear_cached_state_of_remote_branch_only()
2526
self._real_branch.set_last_revision_info(revno, revision_id)
2527
self._last_revision_info_cache = revno, revision_id
2529
if response == ('ok',):
2530
self._clear_cached_state()
2531
self._last_revision_info_cache = revno, revision_id
2532
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2533
# Update the _real_branch's cache too.
2534
if self._real_branch is not None:
2535
cache = self._last_revision_info_cache
2536
self._real_branch._last_revision_info_cache = cache
2538
raise errors.UnexpectedSmartServerResponse(response)
2541
def generate_revision_history(self, revision_id, last_rev=None,
2543
medium = self._client._medium
2544
if not medium._is_remote_before((1, 6)):
2545
# Use a smart method for 1.6 and above servers
2547
self._set_last_revision_descendant(revision_id, other_branch,
2548
allow_diverged=True, allow_overwrite_descendant=True)
2550
except errors.UnknownSmartMethod:
2551
medium._remember_remote_is_before((1, 6))
2552
self._clear_cached_state_of_remote_branch_only()
2553
self.set_revision_history(self._lefthand_history(revision_id,
2554
last_rev=last_rev,other_branch=other_branch))
2556
def set_push_location(self, location):
2558
return self._real_branch.set_push_location(location)
2561
class RemoteConfig(object):
2562
"""A Config that reads and writes from smart verbs.
2564
It is a low-level object that considers config data to be name/value pairs
2565
that may be associated with a section. Assigning meaning to the these
2566
values is done at higher levels like bzrlib.config.TreeConfig.
2569
def get_option(self, name, section=None, default=None):
2570
"""Return the value associated with a named option.
2572
:param name: The name of the value
2573
:param section: The section the option is in (if any)
2574
:param default: The value to return if the value is not set
2575
:return: The value or default value
2578
configobj = self._get_configobj()
2580
section_obj = configobj
2583
section_obj = configobj[section]
2586
return section_obj.get(name, default)
2587
except errors.UnknownSmartMethod:
2588
return self._vfs_get_option(name, section, default)
2590
def _response_to_configobj(self, response):
2591
if len(response[0]) and response[0][0] != 'ok':
2592
raise errors.UnexpectedSmartServerResponse(response)
2593
lines = response[1].read_body_bytes().splitlines()
2594
return config.ConfigObj(lines, encoding='utf-8')
2597
class RemoteBranchConfig(RemoteConfig):
2598
"""A RemoteConfig for Branches."""
2600
def __init__(self, branch):
2601
self._branch = branch
2603
def _get_configobj(self):
2604
path = self._branch._remote_path()
2605
response = self._branch._client.call_expecting_body(
2606
'Branch.get_config_file', path)
2607
return self._response_to_configobj(response)
2609
def set_option(self, value, name, section=None):
2610
"""Set the value associated with a named option.
2612
:param value: The value to set
2613
:param name: The name of the value to set
2614
:param section: The section the option is in (if any)
2616
medium = self._branch._client._medium
2617
if medium._is_remote_before((1, 14)):
2618
return self._vfs_set_option(value, name, section)
2620
path = self._branch._remote_path()
2621
response = self._branch._client.call('Branch.set_config_option',
2622
path, self._branch._lock_token, self._branch._repo_lock_token,
2623
value.encode('utf8'), name, section or '')
2624
except errors.UnknownSmartMethod:
2625
medium._remember_remote_is_before((1, 14))
2626
return self._vfs_set_option(value, name, section)
2628
raise errors.UnexpectedSmartServerResponse(response)
2630
def _real_object(self):
2631
self._branch._ensure_real()
2632
return self._branch._real_branch
2634
def _vfs_set_option(self, value, name, section=None):
2635
return self._real_object()._get_config().set_option(
2636
value, name, section)
2639
class RemoteBzrDirConfig(RemoteConfig):
2640
"""A RemoteConfig for BzrDirs."""
2642
def __init__(self, bzrdir):
2643
self._bzrdir = bzrdir
2645
def _get_configobj(self):
2646
medium = self._bzrdir._client._medium
2647
verb = 'BzrDir.get_config_file'
2648
if medium._is_remote_before((1, 15)):
2649
raise errors.UnknownSmartMethod(verb)
2650
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2651
response = self._bzrdir._call_expecting_body(
2653
return self._response_to_configobj(response)
2655
def _vfs_get_option(self, name, section, default):
2656
return self._real_object()._get_config().get_option(
2657
name, section, default)
2659
def set_option(self, value, name, section=None):
2660
"""Set the value associated with a named option.
2662
:param value: The value to set
2663
:param name: The name of the value to set
2664
:param section: The section the option is in (if any)
2666
return self._real_object()._get_config().set_option(
2667
value, name, section)
2669
def _real_object(self):
2670
self._bzrdir._ensure_real()
2671
return self._bzrdir._real_bzrdir
2675
def _extract_tar(tar, to_dir):
2676
"""Extract all the contents of a tarfile object.
2678
A replacement for extractall, which is not present in python2.4
2681
tar.extract(tarinfo, to_dir)
2684
def _translate_error(err, **context):
2685
"""Translate an ErrorFromSmartServer into a more useful error.
2687
Possible context keys:
2695
If the error from the server doesn't match a known pattern, then
2696
UnknownErrorFromSmartServer is raised.
2700
return context[name]
2701
except KeyError, key_err:
2702
mutter('Missing key %r in context %r', key_err.args[0], context)
2705
"""Get the path from the context if present, otherwise use first error
2709
return context['path']
2710
except KeyError, key_err:
2712
return err.error_args[0]
2713
except IndexError, idx_err:
2715
'Missing key %r in context %r', key_err.args[0], context)
2718
if err.error_verb == 'NoSuchRevision':
2719
raise NoSuchRevision(find('branch'), err.error_args[0])
2720
elif err.error_verb == 'nosuchrevision':
2721
raise NoSuchRevision(find('repository'), err.error_args[0])
2722
elif err.error_tuple == ('nobranch',):
2723
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2724
elif err.error_verb == 'norepository':
2725
raise errors.NoRepositoryPresent(find('bzrdir'))
2726
elif err.error_verb == 'LockContention':
2727
raise errors.LockContention('(remote lock)')
2728
elif err.error_verb == 'UnlockableTransport':
2729
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2730
elif err.error_verb == 'LockFailed':
2731
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2732
elif err.error_verb == 'TokenMismatch':
2733
raise errors.TokenMismatch(find('token'), '(remote token)')
2734
elif err.error_verb == 'Diverged':
2735
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2736
elif err.error_verb == 'TipChangeRejected':
2737
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2738
elif err.error_verb == 'UnstackableBranchFormat':
2739
raise errors.UnstackableBranchFormat(*err.error_args)
2740
elif err.error_verb == 'UnstackableRepositoryFormat':
2741
raise errors.UnstackableRepositoryFormat(*err.error_args)
2742
elif err.error_verb == 'NotStacked':
2743
raise errors.NotStacked(branch=find('branch'))
2744
elif err.error_verb == 'PermissionDenied':
2746
if len(err.error_args) >= 2:
2747
extra = err.error_args[1]
2750
raise errors.PermissionDenied(path, extra=extra)
2751
elif err.error_verb == 'ReadError':
2753
raise errors.ReadError(path)
2754
elif err.error_verb == 'NoSuchFile':
2756
raise errors.NoSuchFile(path)
2757
elif err.error_verb == 'FileExists':
2758
raise errors.FileExists(err.error_args[0])
2759
elif err.error_verb == 'DirectoryNotEmpty':
2760
raise errors.DirectoryNotEmpty(err.error_args[0])
2761
elif err.error_verb == 'ShortReadvError':
2762
args = err.error_args
2763
raise errors.ShortReadvError(
2764
args[0], int(args[1]), int(args[2]), int(args[3]))
2765
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2766
encoding = str(err.error_args[0]) # encoding must always be a string
2767
val = err.error_args[1]
2768
start = int(err.error_args[2])
2769
end = int(err.error_args[3])
2770
reason = str(err.error_args[4]) # reason must always be a string
2771
if val.startswith('u:'):
2772
val = val[2:].decode('utf-8')
2773
elif val.startswith('s:'):
2774
val = val[2:].decode('base64')
2775
if err.error_verb == 'UnicodeDecodeError':
2776
raise UnicodeDecodeError(encoding, val, start, end, reason)
2777
elif err.error_verb == 'UnicodeEncodeError':
2778
raise UnicodeEncodeError(encoding, val, start, end, reason)
2779
elif err.error_verb == 'ReadOnlyError':
2780
raise errors.TransportNotPossible('readonly transport')
2781
raise errors.UnknownErrorFromSmartServer(err)