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.
34
revision as _mod_revision,
38
from bzrlib.branch import BranchReferenceFormat
39
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
40
from bzrlib.decorators import needs_read_lock, needs_write_lock
41
from bzrlib.errors import (
45
from bzrlib.lockable_files import LockableFiles
46
from bzrlib.smart import client, vfs, repository as smart_repo
47
from bzrlib.revision import ensure_null, NULL_REVISION
48
from bzrlib.trace import mutter, note, warning
51
class _RpcHelper(object):
52
"""Mixin class that helps with issuing RPCs."""
54
def _call(self, method, *args, **err_context):
56
return self._client.call(method, *args)
57
except errors.ErrorFromSmartServer, err:
58
self._translate_error(err, **err_context)
60
def _call_expecting_body(self, method, *args, **err_context):
62
return self._client.call_expecting_body(method, *args)
63
except errors.ErrorFromSmartServer, err:
64
self._translate_error(err, **err_context)
66
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
69
return self._client.call_with_body_bytes_expecting_body(
70
method, args, body_bytes)
71
except errors.ErrorFromSmartServer, err:
72
self._translate_error(err, **err_context)
75
def response_tuple_to_repo_format(response):
76
"""Convert a response tuple describing a repository format to a format."""
77
format = RemoteRepositoryFormat()
78
format._rich_root_data = (response[0] == 'yes')
79
format._supports_tree_reference = (response[1] == 'yes')
80
format._supports_external_lookups = (response[2] == 'yes')
81
format._network_name = response[3]
85
# Note: RemoteBzrDirFormat is in bzrdir.py
87
class RemoteBzrDir(BzrDir, _RpcHelper):
88
"""Control directory on a remote server, accessed via bzr:// or similar."""
90
def __init__(self, transport, format, _client=None):
91
"""Construct a RemoteBzrDir.
93
:param _client: Private parameter for testing. Disables probing and the
96
BzrDir.__init__(self, transport, format)
97
# this object holds a delegated bzrdir that uses file-level operations
98
# to talk to the other side
99
self._real_bzrdir = None
100
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
101
# create_branch for details.
102
self._next_open_branch_result = None
105
medium = transport.get_smart_medium()
106
self._client = client._SmartClient(medium)
108
self._client = _client
111
path = self._path_for_remote_call(self._client)
112
response = self._call('BzrDir.open', path)
113
if response not in [('yes',), ('no',)]:
114
raise errors.UnexpectedSmartServerResponse(response)
115
if response == ('no',):
116
raise errors.NotBranchError(path=transport.base)
118
def _ensure_real(self):
119
"""Ensure that there is a _real_bzrdir set.
121
Used before calls to self._real_bzrdir.
123
if not self._real_bzrdir:
124
self._real_bzrdir = BzrDir.open_from_transport(
125
self.root_transport, _server_formats=False)
126
self._format._network_name = \
127
self._real_bzrdir._format.network_name()
129
def _translate_error(self, err, **context):
130
_translate_error(err, bzrdir=self, **context)
132
def break_lock(self):
133
# Prevent aliasing problems in the next_open_branch_result cache.
134
# See create_branch for rationale.
135
self._next_open_branch_result = None
136
return BzrDir.break_lock(self)
138
def _vfs_cloning_metadir(self, require_stacking=False):
140
return self._real_bzrdir.cloning_metadir(
141
require_stacking=require_stacking)
143
def cloning_metadir(self, require_stacking=False):
144
medium = self._client._medium
145
if medium._is_remote_before((1, 13)):
146
return self._vfs_cloning_metadir(require_stacking=require_stacking)
147
verb = 'BzrDir.cloning_metadir'
152
path = self._path_for_remote_call(self._client)
154
response = self._call(verb, path, stacking)
155
except errors.UnknownSmartMethod:
156
medium._remember_remote_is_before((1, 13))
157
return self._vfs_cloning_metadir(require_stacking=require_stacking)
158
except errors.UnknownErrorFromSmartServer, err:
159
if err.error_tuple != ('BranchReference',):
161
# We need to resolve the branch reference to determine the
162
# cloning_metadir. This causes unnecessary RPCs to open the
163
# referenced branch (and bzrdir, etc) but only when the caller
164
# didn't already resolve the branch reference.
165
referenced_branch = self.open_branch()
166
return referenced_branch.bzrdir.cloning_metadir()
167
if len(response) != 3:
168
raise errors.UnexpectedSmartServerResponse(response)
169
control_name, repo_name, branch_info = response
170
if len(branch_info) != 2:
171
raise errors.UnexpectedSmartServerResponse(response)
172
branch_ref, branch_name = branch_info
173
format = bzrdir.network_format_registry.get(control_name)
175
format.repository_format = repository.network_format_registry.get(
177
if branch_ref == 'ref':
178
# XXX: we need possible_transports here to avoid reopening the
179
# connection to the referenced location
180
ref_bzrdir = BzrDir.open(branch_name)
181
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
182
format.set_branch_format(branch_format)
183
elif branch_ref == 'branch':
185
format.set_branch_format(
186
branch.network_format_registry.get(branch_name))
188
raise errors.UnexpectedSmartServerResponse(response)
191
def create_repository(self, shared=False):
192
# as per meta1 formats - just delegate to the format object which may
194
result = self._format.repository_format.initialize(self, shared)
195
if not isinstance(result, RemoteRepository):
196
return self.open_repository()
200
def destroy_repository(self):
201
"""See BzrDir.destroy_repository"""
203
self._real_bzrdir.destroy_repository()
205
def create_branch(self):
206
# as per meta1 formats - just delegate to the format object which may
208
real_branch = self._format.get_branch_format().initialize(self)
209
if not isinstance(real_branch, RemoteBranch):
210
result = RemoteBranch(self, self.find_repository(), real_branch)
213
# BzrDir.clone_on_transport() uses the result of create_branch but does
214
# not return it to its callers; we save approximately 8% of our round
215
# trips by handing the branch we created back to the first caller to
216
# open_branch rather than probing anew. Long term we need a API in
217
# bzrdir that doesn't discard result objects (like result_branch).
219
self._next_open_branch_result = result
222
def destroy_branch(self):
223
"""See BzrDir.destroy_branch"""
225
self._real_bzrdir.destroy_branch()
226
self._next_open_branch_result = None
228
def create_workingtree(self, revision_id=None, from_branch=None):
229
raise errors.NotLocalUrl(self.transport.base)
231
def find_branch_format(self):
232
"""Find the branch 'format' for this bzrdir.
234
This might be a synthetic object for e.g. RemoteBranch and SVN.
236
b = self.open_branch()
239
def get_branch_reference(self):
240
"""See BzrDir.get_branch_reference()."""
241
response = self._get_branch_reference()
242
if response[0] == 'ref':
247
def _get_branch_reference(self):
248
path = self._path_for_remote_call(self._client)
249
medium = self._client._medium
250
if not medium._is_remote_before((1, 13)):
252
response = self._call('BzrDir.open_branchV2', path)
253
if response[0] not in ('ref', 'branch'):
254
raise errors.UnexpectedSmartServerResponse(response)
256
except errors.UnknownSmartMethod:
257
medium._remember_remote_is_before((1, 13))
258
response = self._call('BzrDir.open_branch', path)
259
if response[0] != 'ok':
260
raise errors.UnexpectedSmartServerResponse(response)
261
if response[1] != '':
262
return ('ref', response[1])
264
return ('branch', '')
266
def _get_tree_branch(self):
267
"""See BzrDir._get_tree_branch()."""
268
return None, self.open_branch()
270
def open_branch(self, _unsupported=False, ignore_fallbacks=False):
272
raise NotImplementedError('unsupported flag support not implemented yet.')
273
if self._next_open_branch_result is not None:
274
# See create_branch for details.
275
result = self._next_open_branch_result
276
self._next_open_branch_result = None
278
response = self._get_branch_reference()
279
if response[0] == 'ref':
280
# a branch reference, use the existing BranchReference logic.
281
format = BranchReferenceFormat()
282
return format.open(self, _found=True, location=response[1],
283
ignore_fallbacks=ignore_fallbacks)
284
branch_format_name = response[1]
285
if not branch_format_name:
286
branch_format_name = None
287
format = RemoteBranchFormat(network_name=branch_format_name)
288
return RemoteBranch(self, self.find_repository(), format=format,
289
setup_stacking=not ignore_fallbacks)
291
def _open_repo_v1(self, path):
292
verb = 'BzrDir.find_repository'
293
response = self._call(verb, path)
294
if response[0] != 'ok':
295
raise errors.UnexpectedSmartServerResponse(response)
296
# servers that only support the v1 method don't support external
299
repo = self._real_bzrdir.open_repository()
300
response = response + ('no', repo._format.network_name())
301
return response, repo
303
def _open_repo_v2(self, path):
304
verb = 'BzrDir.find_repositoryV2'
305
response = self._call(verb, path)
306
if response[0] != 'ok':
307
raise errors.UnexpectedSmartServerResponse(response)
309
repo = self._real_bzrdir.open_repository()
310
response = response + (repo._format.network_name(),)
311
return response, repo
313
def _open_repo_v3(self, path):
314
verb = 'BzrDir.find_repositoryV3'
315
medium = self._client._medium
316
if medium._is_remote_before((1, 13)):
317
raise errors.UnknownSmartMethod(verb)
319
response = self._call(verb, path)
320
except errors.UnknownSmartMethod:
321
medium._remember_remote_is_before((1, 13))
323
if response[0] != 'ok':
324
raise errors.UnexpectedSmartServerResponse(response)
325
return response, None
327
def open_repository(self):
328
path = self._path_for_remote_call(self._client)
330
for probe in [self._open_repo_v3, self._open_repo_v2,
333
response, real_repo = probe(path)
335
except errors.UnknownSmartMethod:
338
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
339
if response[0] != 'ok':
340
raise errors.UnexpectedSmartServerResponse(response)
341
if len(response) != 6:
342
raise SmartProtocolError('incorrect response length %s' % (response,))
343
if response[1] == '':
344
# repo is at this dir.
345
format = response_tuple_to_repo_format(response[2:])
346
# Used to support creating a real format instance when needed.
347
format._creating_bzrdir = self
348
remote_repo = RemoteRepository(self, format)
349
format._creating_repo = remote_repo
350
if real_repo is not None:
351
remote_repo._set_real_repository(real_repo)
354
raise errors.NoRepositoryPresent(self)
356
def open_workingtree(self, recommend_upgrade=True):
358
if self._real_bzrdir.has_workingtree():
359
raise errors.NotLocalUrl(self.root_transport)
361
raise errors.NoWorkingTree(self.root_transport.base)
363
def _path_for_remote_call(self, client):
364
"""Return the path to be used for this bzrdir in a remote call."""
365
return client.remote_path_from_transport(self.root_transport)
367
def get_branch_transport(self, branch_format):
369
return self._real_bzrdir.get_branch_transport(branch_format)
371
def get_repository_transport(self, repository_format):
373
return self._real_bzrdir.get_repository_transport(repository_format)
375
def get_workingtree_transport(self, workingtree_format):
377
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
379
def can_convert_format(self):
380
"""Upgrading of remote bzrdirs is not supported yet."""
383
def needs_format_conversion(self, format=None):
384
"""Upgrading of remote bzrdirs is not supported yet."""
386
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
387
% 'needs_format_conversion(format=None)')
390
def clone(self, url, revision_id=None, force_new_repo=False,
391
preserve_stacking=False):
393
return self._real_bzrdir.clone(url, revision_id=revision_id,
394
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
396
def _get_config(self):
397
return RemoteBzrDirConfig(self)
400
class RemoteRepositoryFormat(repository.RepositoryFormat):
401
"""Format for repositories accessed over a _SmartClient.
403
Instances of this repository are represented by RemoteRepository
406
The RemoteRepositoryFormat is parameterized during construction
407
to reflect the capabilities of the real, remote format. Specifically
408
the attributes rich_root_data and supports_tree_reference are set
409
on a per instance basis, and are not set (and should not be) at
412
:ivar _custom_format: If set, a specific concrete repository format that
413
will be used when initializing a repository with this
414
RemoteRepositoryFormat.
415
:ivar _creating_repo: If set, the repository object that this
416
RemoteRepositoryFormat was created for: it can be called into
417
to obtain data like the network name.
420
_matchingbzrdir = RemoteBzrDirFormat()
423
repository.RepositoryFormat.__init__(self)
424
self._custom_format = None
425
self._network_name = None
426
self._creating_bzrdir = None
427
self._supports_external_lookups = None
428
self._supports_tree_reference = None
429
self._rich_root_data = None
432
def fast_deltas(self):
434
return self._custom_format.fast_deltas
437
def rich_root_data(self):
438
if self._rich_root_data is None:
440
self._rich_root_data = self._custom_format.rich_root_data
441
return self._rich_root_data
444
def supports_external_lookups(self):
445
if self._supports_external_lookups is None:
447
self._supports_external_lookups = \
448
self._custom_format.supports_external_lookups
449
return self._supports_external_lookups
452
def supports_tree_reference(self):
453
if self._supports_tree_reference is None:
455
self._supports_tree_reference = \
456
self._custom_format.supports_tree_reference
457
return self._supports_tree_reference
459
def _vfs_initialize(self, a_bzrdir, shared):
460
"""Helper for common code in initialize."""
461
if self._custom_format:
462
# Custom format requested
463
result = self._custom_format.initialize(a_bzrdir, shared=shared)
464
elif self._creating_bzrdir is not None:
465
# Use the format that the repository we were created to back
467
prior_repo = self._creating_bzrdir.open_repository()
468
prior_repo._ensure_real()
469
result = prior_repo._real_repository._format.initialize(
470
a_bzrdir, shared=shared)
472
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
473
# support remote initialization.
474
# We delegate to a real object at this point (as RemoteBzrDir
475
# delegate to the repository format which would lead to infinite
476
# recursion if we just called a_bzrdir.create_repository.
477
a_bzrdir._ensure_real()
478
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
479
if not isinstance(result, RemoteRepository):
480
return self.open(a_bzrdir)
484
def initialize(self, a_bzrdir, shared=False):
485
# Being asked to create on a non RemoteBzrDir:
486
if not isinstance(a_bzrdir, RemoteBzrDir):
487
return self._vfs_initialize(a_bzrdir, shared)
488
medium = a_bzrdir._client._medium
489
if medium._is_remote_before((1, 13)):
490
return self._vfs_initialize(a_bzrdir, shared)
491
# Creating on a remote bzr dir.
492
# 1) get the network name to use.
493
if self._custom_format:
494
network_name = self._custom_format.network_name()
495
elif self._network_name:
496
network_name = self._network_name
498
# Select the current bzrlib default and ask for that.
499
reference_bzrdir_format = bzrdir.format_registry.get('default')()
500
reference_format = reference_bzrdir_format.repository_format
501
network_name = reference_format.network_name()
502
# 2) try direct creation via RPC
503
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
504
verb = 'BzrDir.create_repository'
510
response = a_bzrdir._call(verb, path, network_name, shared_str)
511
except errors.UnknownSmartMethod:
512
# Fallback - use vfs methods
513
medium._remember_remote_is_before((1, 13))
514
return self._vfs_initialize(a_bzrdir, shared)
516
# Turn the response into a RemoteRepository object.
517
format = response_tuple_to_repo_format(response[1:])
518
# Used to support creating a real format instance when needed.
519
format._creating_bzrdir = a_bzrdir
520
remote_repo = RemoteRepository(a_bzrdir, format)
521
format._creating_repo = remote_repo
524
def open(self, a_bzrdir):
525
if not isinstance(a_bzrdir, RemoteBzrDir):
526
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
527
return a_bzrdir.open_repository()
529
def _ensure_real(self):
530
if self._custom_format is None:
531
self._custom_format = repository.network_format_registry.get(
535
def _fetch_order(self):
537
return self._custom_format._fetch_order
540
def _fetch_uses_deltas(self):
542
return self._custom_format._fetch_uses_deltas
545
def _fetch_reconcile(self):
547
return self._custom_format._fetch_reconcile
549
def get_format_description(self):
550
return 'bzr remote repository'
552
def __eq__(self, other):
553
return self.__class__ is other.__class__
555
def check_conversion_target(self, target_format):
556
if self.rich_root_data and not target_format.rich_root_data:
557
raise errors.BadConversionTarget(
558
'Does not support rich root data.', target_format)
559
if (self.supports_tree_reference and
560
not getattr(target_format, 'supports_tree_reference', False)):
561
raise errors.BadConversionTarget(
562
'Does not support nested trees', target_format)
564
def network_name(self):
565
if self._network_name:
566
return self._network_name
567
self._creating_repo._ensure_real()
568
return self._creating_repo._real_repository._format.network_name()
571
def _serializer(self):
573
return self._custom_format._serializer
576
class RemoteRepository(_RpcHelper):
577
"""Repository accessed over rpc.
579
For the moment most operations are performed using local transport-backed
583
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
584
"""Create a RemoteRepository instance.
586
:param remote_bzrdir: The bzrdir hosting this repository.
587
:param format: The RemoteFormat object to use.
588
:param real_repository: If not None, a local implementation of the
589
repository logic for the repository, usually accessing the data
591
:param _client: Private testing parameter - override the smart client
592
to be used by the repository.
595
self._real_repository = real_repository
597
self._real_repository = None
598
self.bzrdir = remote_bzrdir
600
self._client = remote_bzrdir._client
602
self._client = _client
603
self._format = format
604
self._lock_mode = None
605
self._lock_token = None
607
self._leave_lock = False
608
# Cache of revision parents; misses are cached during read locks, and
609
# write locks when no _real_repository has been set.
610
self._unstacked_provider = graph.CachingParentsProvider(
611
get_parent_map=self._get_parent_map_rpc)
612
self._unstacked_provider.disable_cache()
614
# These depend on the actual remote format, so force them off for
615
# maximum compatibility. XXX: In future these should depend on the
616
# remote repository instance, but this is irrelevant until we perform
617
# reconcile via an RPC call.
618
self._reconcile_does_inventory_gc = False
619
self._reconcile_fixes_text_parents = False
620
self._reconcile_backsup_inventory = False
621
self.base = self.bzrdir.transport.base
622
# Additional places to query for data.
623
self._fallback_repositories = []
626
return "%s(%s)" % (self.__class__.__name__, self.base)
630
def abort_write_group(self, suppress_errors=False):
631
"""Complete a write group on the decorated repository.
633
Smart methods perform operations in a single step so this API
634
is not really applicable except as a compatibility thunk
635
for older plugins that don't use e.g. the CommitBuilder
638
:param suppress_errors: see Repository.abort_write_group.
641
return self._real_repository.abort_write_group(
642
suppress_errors=suppress_errors)
646
"""Decorate the real repository for now.
648
In the long term a full blown network facility is needed to avoid
649
creating a real repository object locally.
652
return self._real_repository.chk_bytes
654
def commit_write_group(self):
655
"""Complete a write group on the decorated repository.
657
Smart methods perform operations in a single step so this API
658
is not really applicable except as a compatibility thunk
659
for older plugins that don't use e.g. the CommitBuilder
663
return self._real_repository.commit_write_group()
665
def resume_write_group(self, tokens):
667
return self._real_repository.resume_write_group(tokens)
669
def suspend_write_group(self):
671
return self._real_repository.suspend_write_group()
673
def get_missing_parent_inventories(self, check_for_missing_texts=True):
675
return self._real_repository.get_missing_parent_inventories(
676
check_for_missing_texts=check_for_missing_texts)
678
def _ensure_real(self):
679
"""Ensure that there is a _real_repository set.
681
Used before calls to self._real_repository.
683
Note that _ensure_real causes many roundtrips to the server which are
684
not desirable, and prevents the use of smart one-roundtrip RPC's to
685
perform complex operations (such as accessing parent data, streaming
686
revisions etc). Adding calls to _ensure_real should only be done when
687
bringing up new functionality, adding fallbacks for smart methods that
688
require a fallback path, and never to replace an existing smart method
689
invocation. If in doubt chat to the bzr network team.
691
if self._real_repository is None:
692
if 'hpss' in debug.debug_flags:
694
warning('VFS Repository access triggered\n%s',
695
''.join(traceback.format_stack()))
696
self._unstacked_provider.missing_keys.clear()
697
self.bzrdir._ensure_real()
698
self._set_real_repository(
699
self.bzrdir._real_bzrdir.open_repository())
701
def _translate_error(self, err, **context):
702
self.bzrdir._translate_error(err, repository=self, **context)
704
def find_text_key_references(self):
705
"""Find the text key references within the repository.
707
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
708
revision_ids. Each altered file-ids has the exact revision_ids that
709
altered it listed explicitly.
710
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
711
to whether they were referred to by the inventory of the
712
revision_id that they contain. The inventory texts from all present
713
revision ids are assessed to generate this report.
716
return self._real_repository.find_text_key_references()
718
def _generate_text_key_index(self):
719
"""Generate a new text key index for the repository.
721
This is an expensive function that will take considerable time to run.
723
:return: A dict mapping (file_id, revision_id) tuples to a list of
724
parents, also (file_id, revision_id) tuples.
727
return self._real_repository._generate_text_key_index()
729
def _get_revision_graph(self, revision_id):
730
"""Private method for using with old (< 1.2) servers to fallback."""
731
if revision_id is None:
733
elif revision.is_null(revision_id):
736
path = self.bzrdir._path_for_remote_call(self._client)
737
response = self._call_expecting_body(
738
'Repository.get_revision_graph', path, revision_id)
739
response_tuple, response_handler = response
740
if response_tuple[0] != 'ok':
741
raise errors.UnexpectedSmartServerResponse(response_tuple)
742
coded = response_handler.read_body_bytes()
744
# no revisions in this repository!
746
lines = coded.split('\n')
749
d = tuple(line.split())
750
revision_graph[d[0]] = d[1:]
752
return revision_graph
755
"""See Repository._get_sink()."""
756
return RemoteStreamSink(self)
758
def _get_source(self, to_format):
759
"""Return a source for streaming from this repository."""
760
return RemoteStreamSource(self, to_format)
763
def has_revision(self, revision_id):
764
"""True if this repository has a copy of the revision."""
765
# Copy of bzrlib.repository.Repository.has_revision
766
return revision_id in self.has_revisions((revision_id,))
769
def has_revisions(self, revision_ids):
770
"""Probe to find out the presence of multiple revisions.
772
:param revision_ids: An iterable of revision_ids.
773
:return: A set of the revision_ids that were present.
775
# Copy of bzrlib.repository.Repository.has_revisions
776
parent_map = self.get_parent_map(revision_ids)
777
result = set(parent_map)
778
if _mod_revision.NULL_REVISION in revision_ids:
779
result.add(_mod_revision.NULL_REVISION)
782
def has_same_location(self, other):
783
return (self.__class__ is other.__class__ and
784
self.bzrdir.transport.base == other.bzrdir.transport.base)
786
def get_graph(self, other_repository=None):
787
"""Return the graph for this repository format"""
788
parents_provider = self._make_parents_provider(other_repository)
789
return graph.Graph(parents_provider)
791
def gather_stats(self, revid=None, committers=None):
792
"""See Repository.gather_stats()."""
793
path = self.bzrdir._path_for_remote_call(self._client)
794
# revid can be None to indicate no revisions, not just NULL_REVISION
795
if revid is None or revision.is_null(revid):
799
if committers is None or not committers:
800
fmt_committers = 'no'
802
fmt_committers = 'yes'
803
response_tuple, response_handler = self._call_expecting_body(
804
'Repository.gather_stats', path, fmt_revid, fmt_committers)
805
if response_tuple[0] != 'ok':
806
raise errors.UnexpectedSmartServerResponse(response_tuple)
808
body = response_handler.read_body_bytes()
810
for line in body.split('\n'):
813
key, val_text = line.split(':')
814
if key in ('revisions', 'size', 'committers'):
815
result[key] = int(val_text)
816
elif key in ('firstrev', 'latestrev'):
817
values = val_text.split(' ')[1:]
818
result[key] = (float(values[0]), long(values[1]))
822
def find_branches(self, using=False):
823
"""See Repository.find_branches()."""
824
# should be an API call to the server.
826
return self._real_repository.find_branches(using=using)
828
def get_physical_lock_status(self):
829
"""See Repository.get_physical_lock_status()."""
830
# should be an API call to the server.
832
return self._real_repository.get_physical_lock_status()
834
def is_in_write_group(self):
835
"""Return True if there is an open write group.
837
write groups are only applicable locally for the smart server..
839
if self._real_repository:
840
return self._real_repository.is_in_write_group()
843
return self._lock_count >= 1
846
"""See Repository.is_shared()."""
847
path = self.bzrdir._path_for_remote_call(self._client)
848
response = self._call('Repository.is_shared', path)
849
if response[0] not in ('yes', 'no'):
850
raise SmartProtocolError('unexpected response code %s' % (response,))
851
return response[0] == 'yes'
853
def is_write_locked(self):
854
return self._lock_mode == 'w'
857
# wrong eventually - want a local lock cache context
858
if not self._lock_mode:
859
self._lock_mode = 'r'
861
self._unstacked_provider.enable_cache(cache_misses=True)
862
if self._real_repository is not None:
863
self._real_repository.lock_read()
864
for repo in self._fallback_repositories:
867
self._lock_count += 1
869
def _remote_lock_write(self, token):
870
path = self.bzrdir._path_for_remote_call(self._client)
873
err_context = {'token': token}
874
response = self._call('Repository.lock_write', path, token,
876
if response[0] == 'ok':
880
raise errors.UnexpectedSmartServerResponse(response)
882
def lock_write(self, token=None, _skip_rpc=False):
883
if not self._lock_mode:
885
if self._lock_token is not None:
886
if token != self._lock_token:
887
raise errors.TokenMismatch(token, self._lock_token)
888
self._lock_token = token
890
self._lock_token = self._remote_lock_write(token)
891
# if self._lock_token is None, then this is something like packs or
892
# svn where we don't get to lock the repo, or a weave style repository
893
# where we cannot lock it over the wire and attempts to do so will
895
if self._real_repository is not None:
896
self._real_repository.lock_write(token=self._lock_token)
897
if token is not None:
898
self._leave_lock = True
900
self._leave_lock = False
901
self._lock_mode = 'w'
903
cache_misses = self._real_repository is None
904
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
905
for repo in self._fallback_repositories:
906
# Writes don't affect fallback repos
908
elif self._lock_mode == 'r':
909
raise errors.ReadOnlyError(self)
911
self._lock_count += 1
912
return self._lock_token or None
914
def leave_lock_in_place(self):
915
if not self._lock_token:
916
raise NotImplementedError(self.leave_lock_in_place)
917
self._leave_lock = True
919
def dont_leave_lock_in_place(self):
920
if not self._lock_token:
921
raise NotImplementedError(self.dont_leave_lock_in_place)
922
self._leave_lock = False
924
def _set_real_repository(self, repository):
925
"""Set the _real_repository for this repository.
927
:param repository: The repository to fallback to for non-hpss
928
implemented operations.
930
if self._real_repository is not None:
931
# Replacing an already set real repository.
932
# We cannot do this [currently] if the repository is locked -
933
# synchronised state might be lost.
935
raise AssertionError('_real_repository is already set')
936
if isinstance(repository, RemoteRepository):
937
raise AssertionError()
938
self._real_repository = repository
939
# three code paths happen here:
940
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
941
# up stacking. In this case self._fallback_repositories is [], and the
942
# real repo is already setup. Preserve the real repo and
943
# RemoteRepository.add_fallback_repository will avoid adding
945
# 2) new servers, RemoteBranch.open() sets up stacking, and when
946
# ensure_real is triggered from a branch, the real repository to
947
# set already has a matching list with separate instances, but
948
# as they are also RemoteRepositories we don't worry about making the
949
# lists be identical.
950
# 3) new servers, RemoteRepository.ensure_real is triggered before
951
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
952
# and need to populate it.
953
if (self._fallback_repositories and
954
len(self._real_repository._fallback_repositories) !=
955
len(self._fallback_repositories)):
956
if len(self._real_repository._fallback_repositories):
957
raise AssertionError(
958
"cannot cleanly remove existing _fallback_repositories")
959
for fb in self._fallback_repositories:
960
self._real_repository.add_fallback_repository(fb)
961
if self._lock_mode == 'w':
962
# if we are already locked, the real repository must be able to
963
# acquire the lock with our token.
964
self._real_repository.lock_write(self._lock_token)
965
elif self._lock_mode == 'r':
966
self._real_repository.lock_read()
968
def start_write_group(self):
969
"""Start a write group on the decorated repository.
971
Smart methods perform operations in a single step so this API
972
is not really applicable except as a compatibility thunk
973
for older plugins that don't use e.g. the CommitBuilder
977
return self._real_repository.start_write_group()
979
def _unlock(self, token):
980
path = self.bzrdir._path_for_remote_call(self._client)
982
# with no token the remote repository is not persistently locked.
984
err_context = {'token': token}
985
response = self._call('Repository.unlock', path, token,
987
if response == ('ok',):
990
raise errors.UnexpectedSmartServerResponse(response)
993
if not self._lock_count:
994
raise errors.LockNotHeld(self)
995
self._lock_count -= 1
996
if self._lock_count > 0:
998
self._unstacked_provider.disable_cache()
999
old_mode = self._lock_mode
1000
self._lock_mode = None
1002
# The real repository is responsible at present for raising an
1003
# exception if it's in an unfinished write group. However, it
1004
# normally will *not* actually remove the lock from disk - that's
1005
# done by the server on receiving the Repository.unlock call.
1006
# This is just to let the _real_repository stay up to date.
1007
if self._real_repository is not None:
1008
self._real_repository.unlock()
1010
# The rpc-level lock should be released even if there was a
1011
# problem releasing the vfs-based lock.
1013
# Only write-locked repositories need to make a remote method
1014
# call to perform the unlock.
1015
old_token = self._lock_token
1016
self._lock_token = None
1017
if not self._leave_lock:
1018
self._unlock(old_token)
1019
# Fallbacks are always 'lock_read()' so we don't pay attention to
1021
for repo in self._fallback_repositories:
1024
def break_lock(self):
1025
# should hand off to the network
1027
return self._real_repository.break_lock()
1029
def _get_tarball(self, compression):
1030
"""Return a TemporaryFile containing a repository tarball.
1032
Returns None if the server does not support sending tarballs.
1035
path = self.bzrdir._path_for_remote_call(self._client)
1037
response, protocol = self._call_expecting_body(
1038
'Repository.tarball', path, compression)
1039
except errors.UnknownSmartMethod:
1040
protocol.cancel_read_body()
1042
if response[0] == 'ok':
1043
# Extract the tarball and return it
1044
t = tempfile.NamedTemporaryFile()
1045
# TODO: rpc layer should read directly into it...
1046
t.write(protocol.read_body_bytes())
1049
raise errors.UnexpectedSmartServerResponse(response)
1051
def sprout(self, to_bzrdir, revision_id=None):
1052
# TODO: Option to control what format is created?
1054
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1056
dest_repo.fetch(self, revision_id=revision_id)
1059
### These methods are just thin shims to the VFS object for now.
1061
def revision_tree(self, revision_id):
1063
return self._real_repository.revision_tree(revision_id)
1065
def get_serializer_format(self):
1067
return self._real_repository.get_serializer_format()
1069
def get_commit_builder(self, branch, parents, config, timestamp=None,
1070
timezone=None, committer=None, revprops=None,
1072
# FIXME: It ought to be possible to call this without immediately
1073
# triggering _ensure_real. For now it's the easiest thing to do.
1075
real_repo = self._real_repository
1076
builder = real_repo.get_commit_builder(branch, parents,
1077
config, timestamp=timestamp, timezone=timezone,
1078
committer=committer, revprops=revprops, revision_id=revision_id)
1081
def add_fallback_repository(self, repository):
1082
"""Add a repository to use for looking up data not held locally.
1084
:param repository: A repository.
1086
if not self._format.supports_external_lookups:
1087
raise errors.UnstackableRepositoryFormat(
1088
self._format.network_name(), self.base)
1089
# We need to accumulate additional repositories here, to pass them in
1092
if self.is_locked():
1093
# We will call fallback.unlock() when we transition to the unlocked
1094
# state, so always add a lock here. If a caller passes us a locked
1095
# repository, they are responsible for unlocking it later.
1096
repository.lock_read()
1097
self._fallback_repositories.append(repository)
1098
# If self._real_repository was parameterised already (e.g. because a
1099
# _real_branch had its get_stacked_on_url method called), then the
1100
# repository to be added may already be in the _real_repositories list.
1101
if self._real_repository is not None:
1102
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1103
self._real_repository._fallback_repositories]
1104
if repository.bzrdir.root_transport.base not in fallback_locations:
1105
self._real_repository.add_fallback_repository(repository)
1107
def add_inventory(self, revid, inv, parents):
1109
return self._real_repository.add_inventory(revid, inv, parents)
1111
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1114
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1115
delta, new_revision_id, parents)
1117
def add_revision(self, rev_id, rev, inv=None, config=None):
1119
return self._real_repository.add_revision(
1120
rev_id, rev, inv=inv, config=config)
1123
def get_inventory(self, revision_id):
1125
return self._real_repository.get_inventory(revision_id)
1127
def iter_inventories(self, revision_ids):
1129
return self._real_repository.iter_inventories(revision_ids)
1132
def get_revision(self, revision_id):
1134
return self._real_repository.get_revision(revision_id)
1136
def get_transaction(self):
1138
return self._real_repository.get_transaction()
1141
def clone(self, a_bzrdir, revision_id=None):
1143
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1145
def make_working_trees(self):
1146
"""See Repository.make_working_trees"""
1148
return self._real_repository.make_working_trees()
1150
def refresh_data(self):
1151
"""Re-read any data needed to to synchronise with disk.
1153
This method is intended to be called after another repository instance
1154
(such as one used by a smart server) has inserted data into the
1155
repository. It may not be called during a write group, but may be
1156
called at any other time.
1158
if self.is_in_write_group():
1159
raise errors.InternalBzrError(
1160
"May not refresh_data while in a write group.")
1161
if self._real_repository is not None:
1162
self._real_repository.refresh_data()
1164
def revision_ids_to_search_result(self, result_set):
1165
"""Convert a set of revision ids to a graph SearchResult."""
1166
result_parents = set()
1167
for parents in self.get_graph().get_parent_map(
1168
result_set).itervalues():
1169
result_parents.update(parents)
1170
included_keys = result_set.intersection(result_parents)
1171
start_keys = result_set.difference(included_keys)
1172
exclude_keys = result_parents.difference(result_set)
1173
result = graph.SearchResult(start_keys, exclude_keys,
1174
len(result_set), result_set)
1178
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1179
"""Return the revision ids that other has that this does not.
1181
These are returned in topological order.
1183
revision_id: only return revision ids included by revision_id.
1185
return repository.InterRepository.get(
1186
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1188
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1190
# No base implementation to use as RemoteRepository is not a subclass
1191
# of Repository; so this is a copy of Repository.fetch().
1192
if fetch_spec is not None and revision_id is not None:
1193
raise AssertionError(
1194
"fetch_spec and revision_id are mutually exclusive.")
1195
if self.is_in_write_group():
1196
raise errors.InternalBzrError(
1197
"May not fetch while in a write group.")
1198
# fast path same-url fetch operations
1199
if self.has_same_location(source) and fetch_spec is None:
1200
# check that last_revision is in 'from' and then return a
1202
if (revision_id is not None and
1203
not revision.is_null(revision_id)):
1204
self.get_revision(revision_id)
1206
# if there is no specific appropriate InterRepository, this will get
1207
# the InterRepository base class, which raises an
1208
# IncompatibleRepositories when asked to fetch.
1209
inter = repository.InterRepository.get(source, self)
1210
return inter.fetch(revision_id=revision_id, pb=pb,
1211
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1213
def create_bundle(self, target, base, fileobj, format=None):
1215
self._real_repository.create_bundle(target, base, fileobj, format)
1218
def get_ancestry(self, revision_id, topo_sorted=True):
1220
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1222
def fileids_altered_by_revision_ids(self, revision_ids):
1224
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1226
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1228
return self._real_repository._get_versioned_file_checker(
1229
revisions, revision_versions_cache)
1231
def iter_files_bytes(self, desired_files):
1232
"""See Repository.iter_file_bytes.
1235
return self._real_repository.iter_files_bytes(desired_files)
1237
def get_parent_map(self, revision_ids):
1238
"""See bzrlib.Graph.get_parent_map()."""
1239
return self._make_parents_provider().get_parent_map(revision_ids)
1241
def _get_parent_map_rpc(self, keys):
1242
"""Helper for get_parent_map that performs the RPC."""
1243
medium = self._client._medium
1244
if medium._is_remote_before((1, 2)):
1245
# We already found out that the server can't understand
1246
# Repository.get_parent_map requests, so just fetch the whole
1249
# Note that this reads the whole graph, when only some keys are
1250
# wanted. On this old server there's no way (?) to get them all
1251
# in one go, and the user probably will have seen a warning about
1252
# the server being old anyhow.
1253
rg = self._get_revision_graph(None)
1254
# There is an API discrepancy between get_parent_map and
1255
# get_revision_graph. Specifically, a "key:()" pair in
1256
# get_revision_graph just means a node has no parents. For
1257
# "get_parent_map" it means the node is a ghost. So fix up the
1258
# graph to correct this.
1259
# https://bugs.launchpad.net/bzr/+bug/214894
1260
# There is one other "bug" which is that ghosts in
1261
# get_revision_graph() are not returned at all. But we won't worry
1262
# about that for now.
1263
for node_id, parent_ids in rg.iteritems():
1264
if parent_ids == ():
1265
rg[node_id] = (NULL_REVISION,)
1266
rg[NULL_REVISION] = ()
1271
raise ValueError('get_parent_map(None) is not valid')
1272
if NULL_REVISION in keys:
1273
keys.discard(NULL_REVISION)
1274
found_parents = {NULL_REVISION:()}
1276
return found_parents
1279
# TODO(Needs analysis): We could assume that the keys being requested
1280
# from get_parent_map are in a breadth first search, so typically they
1281
# will all be depth N from some common parent, and we don't have to
1282
# have the server iterate from the root parent, but rather from the
1283
# keys we're searching; and just tell the server the keyspace we
1284
# already have; but this may be more traffic again.
1286
# Transform self._parents_map into a search request recipe.
1287
# TODO: Manage this incrementally to avoid covering the same path
1288
# repeatedly. (The server will have to on each request, but the less
1289
# work done the better).
1291
# Negative caching notes:
1292
# new server sends missing when a request including the revid
1293
# 'include-missing:' is present in the request.
1294
# missing keys are serialised as missing:X, and we then call
1295
# provider.note_missing(X) for-all X
1296
parents_map = self._unstacked_provider.get_cached_map()
1297
if parents_map is None:
1298
# Repository is not locked, so there's no cache.
1300
# start_set is all the keys in the cache
1301
start_set = set(parents_map)
1302
# result set is all the references to keys in the cache
1303
result_parents = set()
1304
for parents in parents_map.itervalues():
1305
result_parents.update(parents)
1306
stop_keys = result_parents.difference(start_set)
1307
# We don't need to send ghosts back to the server as a position to
1309
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1310
key_count = len(parents_map)
1311
if (NULL_REVISION in result_parents
1312
and NULL_REVISION in self._unstacked_provider.missing_keys):
1313
# If we pruned NULL_REVISION from the stop_keys because it's also
1314
# in our cache of "missing" keys we need to increment our key count
1315
# by 1, because the reconsitituted SearchResult on the server will
1316
# still consider NULL_REVISION to be an included key.
1318
included_keys = start_set.intersection(result_parents)
1319
start_set.difference_update(included_keys)
1320
recipe = ('manual', start_set, stop_keys, key_count)
1321
body = self._serialise_search_recipe(recipe)
1322
path = self.bzrdir._path_for_remote_call(self._client)
1324
if type(key) is not str:
1326
"key %r not a plain string" % (key,))
1327
verb = 'Repository.get_parent_map'
1328
args = (path, 'include-missing:') + tuple(keys)
1330
response = self._call_with_body_bytes_expecting_body(
1332
except errors.UnknownSmartMethod:
1333
# Server does not support this method, so get the whole graph.
1334
# Worse, we have to force a disconnection, because the server now
1335
# doesn't realise it has a body on the wire to consume, so the
1336
# only way to recover is to abandon the connection.
1338
'Server is too old for fast get_parent_map, reconnecting. '
1339
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1341
# To avoid having to disconnect repeatedly, we keep track of the
1342
# fact the server doesn't understand remote methods added in 1.2.
1343
medium._remember_remote_is_before((1, 2))
1344
# Recurse just once and we should use the fallback code.
1345
return self._get_parent_map_rpc(keys)
1346
response_tuple, response_handler = response
1347
if response_tuple[0] not in ['ok']:
1348
response_handler.cancel_read_body()
1349
raise errors.UnexpectedSmartServerResponse(response_tuple)
1350
if response_tuple[0] == 'ok':
1351
coded = bz2.decompress(response_handler.read_body_bytes())
1353
# no revisions found
1355
lines = coded.split('\n')
1358
d = tuple(line.split())
1360
revision_graph[d[0]] = d[1:]
1363
if d[0].startswith('missing:'):
1365
self._unstacked_provider.note_missing_key(revid)
1367
# no parents - so give the Graph result
1369
revision_graph[d[0]] = (NULL_REVISION,)
1370
return revision_graph
1373
def get_signature_text(self, revision_id):
1375
return self._real_repository.get_signature_text(revision_id)
1378
def get_inventory_xml(self, revision_id):
1380
return self._real_repository.get_inventory_xml(revision_id)
1382
def deserialise_inventory(self, revision_id, xml):
1384
return self._real_repository.deserialise_inventory(revision_id, xml)
1386
def reconcile(self, other=None, thorough=False):
1388
return self._real_repository.reconcile(other=other, thorough=thorough)
1390
def all_revision_ids(self):
1392
return self._real_repository.all_revision_ids()
1395
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1397
return self._real_repository.get_deltas_for_revisions(revisions,
1398
specific_fileids=specific_fileids)
1401
def get_revision_delta(self, revision_id, specific_fileids=None):
1403
return self._real_repository.get_revision_delta(revision_id,
1404
specific_fileids=specific_fileids)
1407
def revision_trees(self, revision_ids):
1409
return self._real_repository.revision_trees(revision_ids)
1412
def get_revision_reconcile(self, revision_id):
1414
return self._real_repository.get_revision_reconcile(revision_id)
1417
def check(self, revision_ids=None):
1419
return self._real_repository.check(revision_ids=revision_ids)
1421
def copy_content_into(self, destination, revision_id=None):
1423
return self._real_repository.copy_content_into(
1424
destination, revision_id=revision_id)
1426
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1427
# get a tarball of the remote repository, and copy from that into the
1429
from bzrlib import osutils
1431
# TODO: Maybe a progress bar while streaming the tarball?
1432
note("Copying repository content as tarball...")
1433
tar_file = self._get_tarball('bz2')
1434
if tar_file is None:
1436
destination = to_bzrdir.create_repository()
1438
tar = tarfile.open('repository', fileobj=tar_file,
1440
tmpdir = osutils.mkdtemp()
1442
_extract_tar(tar, tmpdir)
1443
tmp_bzrdir = BzrDir.open(tmpdir)
1444
tmp_repo = tmp_bzrdir.open_repository()
1445
tmp_repo.copy_content_into(destination, revision_id)
1447
osutils.rmtree(tmpdir)
1451
# TODO: Suggestion from john: using external tar is much faster than
1452
# python's tarfile library, but it may not work on windows.
1455
def inventories(self):
1456
"""Decorate the real repository for now.
1458
In the long term a full blown network facility is needed to
1459
avoid creating a real repository object locally.
1462
return self._real_repository.inventories
1466
"""Compress the data within the repository.
1468
This is not currently implemented within the smart server.
1471
return self._real_repository.pack()
1474
def revisions(self):
1475
"""Decorate the real repository for now.
1477
In the short term this should become a real object to intercept graph
1480
In the long term a full blown network facility is needed.
1483
return self._real_repository.revisions
1485
def set_make_working_trees(self, new_value):
1487
new_value_str = "True"
1489
new_value_str = "False"
1490
path = self.bzrdir._path_for_remote_call(self._client)
1492
response = self._call(
1493
'Repository.set_make_working_trees', path, new_value_str)
1494
except errors.UnknownSmartMethod:
1496
self._real_repository.set_make_working_trees(new_value)
1498
if response[0] != 'ok':
1499
raise errors.UnexpectedSmartServerResponse(response)
1502
def signatures(self):
1503
"""Decorate the real repository for now.
1505
In the long term a full blown network facility is needed to avoid
1506
creating a real repository object locally.
1509
return self._real_repository.signatures
1512
def sign_revision(self, revision_id, gpg_strategy):
1514
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1518
"""Decorate the real repository for now.
1520
In the long term a full blown network facility is needed to avoid
1521
creating a real repository object locally.
1524
return self._real_repository.texts
1527
def get_revisions(self, revision_ids):
1529
return self._real_repository.get_revisions(revision_ids)
1531
def supports_rich_root(self):
1532
return self._format.rich_root_data
1534
def iter_reverse_revision_history(self, revision_id):
1536
return self._real_repository.iter_reverse_revision_history(revision_id)
1539
def _serializer(self):
1540
return self._format._serializer
1542
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1544
return self._real_repository.store_revision_signature(
1545
gpg_strategy, plaintext, revision_id)
1547
def add_signature_text(self, revision_id, signature):
1549
return self._real_repository.add_signature_text(revision_id, signature)
1551
def has_signature_for_revision_id(self, revision_id):
1553
return self._real_repository.has_signature_for_revision_id(revision_id)
1555
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1557
return self._real_repository.item_keys_introduced_by(revision_ids,
1558
_files_pb=_files_pb)
1560
def revision_graph_can_have_wrong_parents(self):
1561
# The answer depends on the remote repo format.
1563
return self._real_repository.revision_graph_can_have_wrong_parents()
1565
def _find_inconsistent_revision_parents(self):
1567
return self._real_repository._find_inconsistent_revision_parents()
1569
def _check_for_inconsistent_revision_parents(self):
1571
return self._real_repository._check_for_inconsistent_revision_parents()
1573
def _make_parents_provider(self, other=None):
1574
providers = [self._unstacked_provider]
1575
if other is not None:
1576
providers.insert(0, other)
1577
providers.extend(r._make_parents_provider() for r in
1578
self._fallback_repositories)
1579
return graph.StackedParentsProvider(providers)
1581
def _serialise_search_recipe(self, recipe):
1582
"""Serialise a graph search recipe.
1584
:param recipe: A search recipe (start, stop, count).
1585
:return: Serialised bytes.
1587
start_keys = ' '.join(recipe[1])
1588
stop_keys = ' '.join(recipe[2])
1589
count = str(recipe[3])
1590
return '\n'.join((start_keys, stop_keys, count))
1592
def _serialise_search_result(self, search_result):
1593
if isinstance(search_result, graph.PendingAncestryResult):
1594
parts = ['ancestry-of']
1595
parts.extend(search_result.heads)
1597
recipe = search_result.get_recipe()
1598
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1599
return '\n'.join(parts)
1602
path = self.bzrdir._path_for_remote_call(self._client)
1604
response = self._call('PackRepository.autopack', path)
1605
except errors.UnknownSmartMethod:
1607
self._real_repository._pack_collection.autopack()
1610
if response[0] != 'ok':
1611
raise errors.UnexpectedSmartServerResponse(response)
1614
class RemoteStreamSink(repository.StreamSink):
1616
def _insert_real(self, stream, src_format, resume_tokens):
1617
self.target_repo._ensure_real()
1618
sink = self.target_repo._real_repository._get_sink()
1619
result = sink.insert_stream(stream, src_format, resume_tokens)
1621
self.target_repo.autopack()
1624
def insert_stream(self, stream, src_format, resume_tokens):
1625
target = self.target_repo
1626
target._unstacked_provider.missing_keys.clear()
1627
if target._lock_token:
1628
verb = 'Repository.insert_stream_locked'
1629
extra_args = (target._lock_token or '',)
1630
required_version = (1, 14)
1632
verb = 'Repository.insert_stream'
1634
required_version = (1, 13)
1635
client = target._client
1636
medium = client._medium
1637
if medium._is_remote_before(required_version):
1638
# No possible way this can work.
1639
return self._insert_real(stream, src_format, resume_tokens)
1640
path = target.bzrdir._path_for_remote_call(client)
1641
if not resume_tokens:
1642
# XXX: Ugly but important for correctness, *will* be fixed during
1643
# 1.13 cycle. Pushing a stream that is interrupted results in a
1644
# fallback to the _real_repositories sink *with a partial stream*.
1645
# Thats bad because we insert less data than bzr expected. To avoid
1646
# this we do a trial push to make sure the verb is accessible, and
1647
# do not fallback when actually pushing the stream. A cleanup patch
1648
# is going to look at rewinding/restarting the stream/partial
1650
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1652
response = client.call_with_body_stream(
1653
(verb, path, '') + extra_args, byte_stream)
1654
except errors.UnknownSmartMethod:
1655
medium._remember_remote_is_before(required_version)
1656
return self._insert_real(stream, src_format, resume_tokens)
1657
byte_stream = smart_repo._stream_to_byte_stream(
1659
resume_tokens = ' '.join(resume_tokens)
1660
response = client.call_with_body_stream(
1661
(verb, path, resume_tokens) + extra_args, byte_stream)
1662
if response[0][0] not in ('ok', 'missing-basis'):
1663
raise errors.UnexpectedSmartServerResponse(response)
1664
if response[0][0] == 'missing-basis':
1665
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1666
resume_tokens = tokens
1667
return resume_tokens, set(missing_keys)
1669
self.target_repo.refresh_data()
1673
class RemoteStreamSource(repository.StreamSource):
1674
"""Stream data from a remote server."""
1676
def get_stream(self, search):
1677
if (self.from_repository._fallback_repositories and
1678
self.to_format._fetch_order == 'topological'):
1679
return self._real_stream(self.from_repository, search)
1680
return self.missing_parents_chain(search, [self.from_repository] +
1681
self.from_repository._fallback_repositories)
1683
def _real_stream(self, repo, search):
1684
"""Get a stream for search from repo.
1686
This never called RemoteStreamSource.get_stream, and is a heler
1687
for RemoteStreamSource._get_stream to allow getting a stream
1688
reliably whether fallback back because of old servers or trying
1689
to stream from a non-RemoteRepository (which the stacked support
1692
source = repo._get_source(self.to_format)
1693
if isinstance(source, RemoteStreamSource):
1694
return repository.StreamSource.get_stream(source, search)
1695
return source.get_stream(search)
1697
def _get_stream(self, repo, search):
1698
"""Core worker to get a stream from repo for search.
1700
This is used by both get_stream and the stacking support logic. It
1701
deliberately gets a stream for repo which does not need to be
1702
self.from_repository. In the event that repo is not Remote, or
1703
cannot do a smart stream, a fallback is made to the generic
1704
repository._get_stream() interface, via self._real_stream.
1706
In the event of stacking, streams from _get_stream will not
1707
contain all the data for search - this is normal (see get_stream).
1709
:param repo: A repository.
1710
:param search: A search.
1712
# Fallbacks may be non-smart
1713
if not isinstance(repo, RemoteRepository):
1714
return self._real_stream(repo, search)
1715
client = repo._client
1716
medium = client._medium
1717
if medium._is_remote_before((1, 13)):
1718
# streaming was added in 1.13
1719
return self._real_stream(repo, search)
1720
path = repo.bzrdir._path_for_remote_call(client)
1722
search_bytes = repo._serialise_search_result(search)
1723
response = repo._call_with_body_bytes_expecting_body(
1724
'Repository.get_stream',
1725
(path, self.to_format.network_name()), search_bytes)
1726
response_tuple, response_handler = response
1727
except errors.UnknownSmartMethod:
1728
medium._remember_remote_is_before((1,13))
1729
return self._real_stream(repo, search)
1730
if response_tuple[0] != 'ok':
1731
raise errors.UnexpectedSmartServerResponse(response_tuple)
1732
byte_stream = response_handler.read_streamed_body()
1733
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1734
if src_format.network_name() != repo._format.network_name():
1735
raise AssertionError(
1736
"Mismatched RemoteRepository and stream src %r, %r" % (
1737
src_format.network_name(), repo._format.network_name()))
1740
def missing_parents_chain(self, search, sources):
1741
"""Chain multiple streams together to handle stacking.
1743
:param search: The overall search to satisfy with streams.
1744
:param sources: A list of Repository objects to query.
1746
self.serialiser = self.to_format._serializer
1747
self.seen_revs = set()
1748
self.referenced_revs = set()
1749
# If there are heads in the search, or the key count is > 0, we are not
1751
while not search.is_empty() and len(sources) > 1:
1752
source = sources.pop(0)
1753
stream = self._get_stream(source, search)
1754
for kind, substream in stream:
1755
if kind != 'revisions':
1756
yield kind, substream
1758
yield kind, self.missing_parents_rev_handler(substream)
1759
search = search.refine(self.seen_revs, self.referenced_revs)
1760
self.seen_revs = set()
1761
self.referenced_revs = set()
1762
if not search.is_empty():
1763
for kind, stream in self._get_stream(sources[0], search):
1766
def missing_parents_rev_handler(self, substream):
1767
for content in substream:
1768
revision_bytes = content.get_bytes_as('fulltext')
1769
revision = self.serialiser.read_revision_from_string(revision_bytes)
1770
self.seen_revs.add(content.key[-1])
1771
self.referenced_revs.update(revision.parent_ids)
1775
class RemoteBranchLockableFiles(LockableFiles):
1776
"""A 'LockableFiles' implementation that talks to a smart server.
1778
This is not a public interface class.
1781
def __init__(self, bzrdir, _client):
1782
self.bzrdir = bzrdir
1783
self._client = _client
1784
self._need_find_modes = True
1785
LockableFiles.__init__(
1786
self, bzrdir.get_branch_transport(None),
1787
'lock', lockdir.LockDir)
1789
def _find_modes(self):
1790
# RemoteBranches don't let the client set the mode of control files.
1791
self._dir_mode = None
1792
self._file_mode = None
1795
class RemoteBranchFormat(branch.BranchFormat):
1797
def __init__(self, network_name=None):
1798
super(RemoteBranchFormat, self).__init__()
1799
self._matchingbzrdir = RemoteBzrDirFormat()
1800
self._matchingbzrdir.set_branch_format(self)
1801
self._custom_format = None
1802
self._network_name = network_name
1804
def __eq__(self, other):
1805
return (isinstance(other, RemoteBranchFormat) and
1806
self.__dict__ == other.__dict__)
1808
def _ensure_real(self):
1809
if self._custom_format is None:
1810
self._custom_format = branch.network_format_registry.get(
1813
def get_format_description(self):
1814
return 'Remote BZR Branch'
1816
def network_name(self):
1817
return self._network_name
1819
def open(self, a_bzrdir, ignore_fallbacks=False):
1820
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
1822
def _vfs_initialize(self, a_bzrdir):
1823
# Initialisation when using a local bzrdir object, or a non-vfs init
1824
# method is not available on the server.
1825
# self._custom_format is always set - the start of initialize ensures
1827
if isinstance(a_bzrdir, RemoteBzrDir):
1828
a_bzrdir._ensure_real()
1829
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1831
# We assume the bzrdir is parameterised; it may not be.
1832
result = self._custom_format.initialize(a_bzrdir)
1833
if (isinstance(a_bzrdir, RemoteBzrDir) and
1834
not isinstance(result, RemoteBranch)):
1835
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1838
def initialize(self, a_bzrdir):
1839
# 1) get the network name to use.
1840
if self._custom_format:
1841
network_name = self._custom_format.network_name()
1843
# Select the current bzrlib default and ask for that.
1844
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1845
reference_format = reference_bzrdir_format.get_branch_format()
1846
self._custom_format = reference_format
1847
network_name = reference_format.network_name()
1848
# Being asked to create on a non RemoteBzrDir:
1849
if not isinstance(a_bzrdir, RemoteBzrDir):
1850
return self._vfs_initialize(a_bzrdir)
1851
medium = a_bzrdir._client._medium
1852
if medium._is_remote_before((1, 13)):
1853
return self._vfs_initialize(a_bzrdir)
1854
# Creating on a remote bzr dir.
1855
# 2) try direct creation via RPC
1856
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1857
verb = 'BzrDir.create_branch'
1859
response = a_bzrdir._call(verb, path, network_name)
1860
except errors.UnknownSmartMethod:
1861
# Fallback - use vfs methods
1862
medium._remember_remote_is_before((1, 13))
1863
return self._vfs_initialize(a_bzrdir)
1864
if response[0] != 'ok':
1865
raise errors.UnexpectedSmartServerResponse(response)
1866
# Turn the response into a RemoteRepository object.
1867
format = RemoteBranchFormat(network_name=response[1])
1868
repo_format = response_tuple_to_repo_format(response[3:])
1869
if response[2] == '':
1870
repo_bzrdir = a_bzrdir
1872
repo_bzrdir = RemoteBzrDir(
1873
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1875
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1876
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1877
format=format, setup_stacking=False)
1878
# XXX: We know this is a new branch, so it must have revno 0, revid
1879
# NULL_REVISION. Creating the branch locked would make this be unable
1880
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1881
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1882
return remote_branch
1884
def make_tags(self, branch):
1886
return self._custom_format.make_tags(branch)
1888
def supports_tags(self):
1889
# Remote branches might support tags, but we won't know until we
1890
# access the real remote branch.
1892
return self._custom_format.supports_tags()
1894
def supports_stacking(self):
1896
return self._custom_format.supports_stacking()
1899
class RemoteBranch(branch.Branch, _RpcHelper):
1900
"""Branch stored on a server accessed by HPSS RPC.
1902
At the moment most operations are mapped down to simple file operations.
1905
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1906
_client=None, format=None, setup_stacking=True):
1907
"""Create a RemoteBranch instance.
1909
:param real_branch: An optional local implementation of the branch
1910
format, usually accessing the data via the VFS.
1911
:param _client: Private parameter for testing.
1912
:param format: A RemoteBranchFormat object, None to create one
1913
automatically. If supplied it should have a network_name already
1915
:param setup_stacking: If True make an RPC call to determine the
1916
stacked (or not) status of the branch. If False assume the branch
1919
# We intentionally don't call the parent class's __init__, because it
1920
# will try to assign to self.tags, which is a property in this subclass.
1921
# And the parent's __init__ doesn't do much anyway.
1922
self._revision_id_to_revno_cache = None
1923
self._partial_revision_id_to_revno_cache = {}
1924
self._revision_history_cache = None
1925
self._last_revision_info_cache = None
1926
self._merge_sorted_revisions_cache = None
1927
self.bzrdir = remote_bzrdir
1928
if _client is not None:
1929
self._client = _client
1931
self._client = remote_bzrdir._client
1932
self.repository = remote_repository
1933
if real_branch is not None:
1934
self._real_branch = real_branch
1935
# Give the remote repository the matching real repo.
1936
real_repo = self._real_branch.repository
1937
if isinstance(real_repo, RemoteRepository):
1938
real_repo._ensure_real()
1939
real_repo = real_repo._real_repository
1940
self.repository._set_real_repository(real_repo)
1941
# Give the branch the remote repository to let fast-pathing happen.
1942
self._real_branch.repository = self.repository
1944
self._real_branch = None
1945
# Fill out expected attributes of branch for bzrlib API users.
1946
self.base = self.bzrdir.root_transport.base
1947
self._control_files = None
1948
self._lock_mode = None
1949
self._lock_token = None
1950
self._repo_lock_token = None
1951
self._lock_count = 0
1952
self._leave_lock = False
1953
# Setup a format: note that we cannot call _ensure_real until all the
1954
# attributes above are set: This code cannot be moved higher up in this
1957
self._format = RemoteBranchFormat()
1958
if real_branch is not None:
1959
self._format._network_name = \
1960
self._real_branch._format.network_name()
1962
self._format = format
1963
if not self._format._network_name:
1964
# Did not get from open_branchV2 - old server.
1966
self._format._network_name = \
1967
self._real_branch._format.network_name()
1968
self.tags = self._format.make_tags(self)
1969
# The base class init is not called, so we duplicate this:
1970
hooks = branch.Branch.hooks['open']
1973
self._is_stacked = False
1975
self._setup_stacking()
1977
def _setup_stacking(self):
1978
# configure stacking into the remote repository, by reading it from
1981
fallback_url = self.get_stacked_on_url()
1982
except (errors.NotStacked, errors.UnstackableBranchFormat,
1983
errors.UnstackableRepositoryFormat), e:
1985
self._is_stacked = True
1986
self._activate_fallback_location(fallback_url)
1988
def _get_config(self):
1989
return RemoteBranchConfig(self)
1991
def _get_real_transport(self):
1992
# if we try vfs access, return the real branch's vfs transport
1994
return self._real_branch._transport
1996
_transport = property(_get_real_transport)
1999
return "%s(%s)" % (self.__class__.__name__, self.base)
2003
def _ensure_real(self):
2004
"""Ensure that there is a _real_branch set.
2006
Used before calls to self._real_branch.
2008
if self._real_branch is None:
2009
if not vfs.vfs_enabled():
2010
raise AssertionError('smart server vfs must be enabled '
2011
'to use vfs implementation')
2012
self.bzrdir._ensure_real()
2013
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
2014
if self.repository._real_repository is None:
2015
# Give the remote repository the matching real repo.
2016
real_repo = self._real_branch.repository
2017
if isinstance(real_repo, RemoteRepository):
2018
real_repo._ensure_real()
2019
real_repo = real_repo._real_repository
2020
self.repository._set_real_repository(real_repo)
2021
# Give the real branch the remote repository to let fast-pathing
2023
self._real_branch.repository = self.repository
2024
if self._lock_mode == 'r':
2025
self._real_branch.lock_read()
2026
elif self._lock_mode == 'w':
2027
self._real_branch.lock_write(token=self._lock_token)
2029
def _translate_error(self, err, **context):
2030
self.repository._translate_error(err, branch=self, **context)
2032
def _clear_cached_state(self):
2033
super(RemoteBranch, self)._clear_cached_state()
2034
if self._real_branch is not None:
2035
self._real_branch._clear_cached_state()
2037
def _clear_cached_state_of_remote_branch_only(self):
2038
"""Like _clear_cached_state, but doesn't clear the cache of
2041
This is useful when falling back to calling a method of
2042
self._real_branch that changes state. In that case the underlying
2043
branch changes, so we need to invalidate this RemoteBranch's cache of
2044
it. However, there's no need to invalidate the _real_branch's cache
2045
too, in fact doing so might harm performance.
2047
super(RemoteBranch, self)._clear_cached_state()
2050
def control_files(self):
2051
# Defer actually creating RemoteBranchLockableFiles until its needed,
2052
# because it triggers an _ensure_real that we otherwise might not need.
2053
if self._control_files is None:
2054
self._control_files = RemoteBranchLockableFiles(
2055
self.bzrdir, self._client)
2056
return self._control_files
2058
def _get_checkout_format(self):
2060
return self._real_branch._get_checkout_format()
2062
def get_physical_lock_status(self):
2063
"""See Branch.get_physical_lock_status()."""
2064
# should be an API call to the server, as branches must be lockable.
2066
return self._real_branch.get_physical_lock_status()
2068
def get_stacked_on_url(self):
2069
"""Get the URL this branch is stacked against.
2071
:raises NotStacked: If the branch is not stacked.
2072
:raises UnstackableBranchFormat: If the branch does not support
2074
:raises UnstackableRepositoryFormat: If the repository does not support
2078
# there may not be a repository yet, so we can't use
2079
# self._translate_error, so we can't use self._call either.
2080
response = self._client.call('Branch.get_stacked_on_url',
2081
self._remote_path())
2082
except errors.ErrorFromSmartServer, err:
2083
# there may not be a repository yet, so we can't call through
2084
# its _translate_error
2085
_translate_error(err, branch=self)
2086
except errors.UnknownSmartMethod, err:
2088
return self._real_branch.get_stacked_on_url()
2089
if response[0] != 'ok':
2090
raise errors.UnexpectedSmartServerResponse(response)
2093
def set_stacked_on_url(self, url):
2094
branch.Branch.set_stacked_on_url(self, url)
2096
self._is_stacked = False
2098
self._is_stacked = True
2100
def _vfs_get_tags_bytes(self):
2102
return self._real_branch._get_tags_bytes()
2104
def _get_tags_bytes(self):
2105
medium = self._client._medium
2106
if medium._is_remote_before((1, 13)):
2107
return self._vfs_get_tags_bytes()
2109
response = self._call('Branch.get_tags_bytes', self._remote_path())
2110
except errors.UnknownSmartMethod:
2111
medium._remember_remote_is_before((1, 13))
2112
return self._vfs_get_tags_bytes()
2115
def lock_read(self):
2116
self.repository.lock_read()
2117
if not self._lock_mode:
2118
self._lock_mode = 'r'
2119
self._lock_count = 1
2120
if self._real_branch is not None:
2121
self._real_branch.lock_read()
2123
self._lock_count += 1
2125
def _remote_lock_write(self, token):
2127
branch_token = repo_token = ''
2129
branch_token = token
2130
repo_token = self.repository.lock_write()
2131
self.repository.unlock()
2132
err_context = {'token': token}
2133
response = self._call(
2134
'Branch.lock_write', self._remote_path(), branch_token,
2135
repo_token or '', **err_context)
2136
if response[0] != 'ok':
2137
raise errors.UnexpectedSmartServerResponse(response)
2138
ok, branch_token, repo_token = response
2139
return branch_token, repo_token
2141
def lock_write(self, token=None):
2142
if not self._lock_mode:
2143
# Lock the branch and repo in one remote call.
2144
remote_tokens = self._remote_lock_write(token)
2145
self._lock_token, self._repo_lock_token = remote_tokens
2146
if not self._lock_token:
2147
raise SmartProtocolError('Remote server did not return a token!')
2148
# Tell the self.repository object that it is locked.
2149
self.repository.lock_write(
2150
self._repo_lock_token, _skip_rpc=True)
2152
if self._real_branch is not None:
2153
self._real_branch.lock_write(token=self._lock_token)
2154
if token is not None:
2155
self._leave_lock = True
2157
self._leave_lock = False
2158
self._lock_mode = 'w'
2159
self._lock_count = 1
2160
elif self._lock_mode == 'r':
2161
raise errors.ReadOnlyTransaction
2163
if token is not None:
2164
# A token was given to lock_write, and we're relocking, so
2165
# check that the given token actually matches the one we
2167
if token != self._lock_token:
2168
raise errors.TokenMismatch(token, self._lock_token)
2169
self._lock_count += 1
2170
# Re-lock the repository too.
2171
self.repository.lock_write(self._repo_lock_token)
2172
return self._lock_token or None
2174
def _set_tags_bytes(self, bytes):
2176
return self._real_branch._set_tags_bytes(bytes)
2178
def _unlock(self, branch_token, repo_token):
2179
err_context = {'token': str((branch_token, repo_token))}
2180
response = self._call(
2181
'Branch.unlock', self._remote_path(), branch_token,
2182
repo_token or '', **err_context)
2183
if response == ('ok',):
2185
raise errors.UnexpectedSmartServerResponse(response)
2189
self._lock_count -= 1
2190
if not self._lock_count:
2191
self._clear_cached_state()
2192
mode = self._lock_mode
2193
self._lock_mode = None
2194
if self._real_branch is not None:
2195
if (not self._leave_lock and mode == 'w' and
2196
self._repo_lock_token):
2197
# If this RemoteBranch will remove the physical lock
2198
# for the repository, make sure the _real_branch
2199
# doesn't do it first. (Because the _real_branch's
2200
# repository is set to be the RemoteRepository.)
2201
self._real_branch.repository.leave_lock_in_place()
2202
self._real_branch.unlock()
2204
# Only write-locked branched need to make a remote method
2205
# call to perform the unlock.
2207
if not self._lock_token:
2208
raise AssertionError('Locked, but no token!')
2209
branch_token = self._lock_token
2210
repo_token = self._repo_lock_token
2211
self._lock_token = None
2212
self._repo_lock_token = None
2213
if not self._leave_lock:
2214
self._unlock(branch_token, repo_token)
2216
self.repository.unlock()
2218
def break_lock(self):
2220
return self._real_branch.break_lock()
2222
def leave_lock_in_place(self):
2223
if not self._lock_token:
2224
raise NotImplementedError(self.leave_lock_in_place)
2225
self._leave_lock = True
2227
def dont_leave_lock_in_place(self):
2228
if not self._lock_token:
2229
raise NotImplementedError(self.dont_leave_lock_in_place)
2230
self._leave_lock = False
2232
def _last_revision_info(self):
2233
response = self._call('Branch.last_revision_info', self._remote_path())
2234
if response[0] != 'ok':
2235
raise SmartProtocolError('unexpected response code %s' % (response,))
2236
revno = int(response[1])
2237
last_revision = response[2]
2238
return (revno, last_revision)
2240
def _gen_revision_history(self):
2241
"""See Branch._gen_revision_history()."""
2242
if self._is_stacked:
2244
return self._real_branch._gen_revision_history()
2245
response_tuple, response_handler = self._call_expecting_body(
2246
'Branch.revision_history', self._remote_path())
2247
if response_tuple[0] != 'ok':
2248
raise errors.UnexpectedSmartServerResponse(response_tuple)
2249
result = response_handler.read_body_bytes().split('\x00')
2254
def _remote_path(self):
2255
return self.bzrdir._path_for_remote_call(self._client)
2257
def _set_last_revision_descendant(self, revision_id, other_branch,
2258
allow_diverged=False, allow_overwrite_descendant=False):
2259
# This performs additional work to meet the hook contract; while its
2260
# undesirable, we have to synthesise the revno to call the hook, and
2261
# not calling the hook is worse as it means changes can't be prevented.
2262
# Having calculated this though, we can't just call into
2263
# set_last_revision_info as a simple call, because there is a set_rh
2264
# hook that some folk may still be using.
2265
old_revno, old_revid = self.last_revision_info()
2266
history = self._lefthand_history(revision_id)
2267
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2268
err_context = {'other_branch': other_branch}
2269
response = self._call('Branch.set_last_revision_ex',
2270
self._remote_path(), self._lock_token, self._repo_lock_token,
2271
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2273
self._clear_cached_state()
2274
if len(response) != 3 and response[0] != 'ok':
2275
raise errors.UnexpectedSmartServerResponse(response)
2276
new_revno, new_revision_id = response[1:]
2277
self._last_revision_info_cache = new_revno, new_revision_id
2278
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2279
if self._real_branch is not None:
2280
cache = new_revno, new_revision_id
2281
self._real_branch._last_revision_info_cache = cache
2283
def _set_last_revision(self, revision_id):
2284
old_revno, old_revid = self.last_revision_info()
2285
# This performs additional work to meet the hook contract; while its
2286
# undesirable, we have to synthesise the revno to call the hook, and
2287
# not calling the hook is worse as it means changes can't be prevented.
2288
# Having calculated this though, we can't just call into
2289
# set_last_revision_info as a simple call, because there is a set_rh
2290
# hook that some folk may still be using.
2291
history = self._lefthand_history(revision_id)
2292
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2293
self._clear_cached_state()
2294
response = self._call('Branch.set_last_revision',
2295
self._remote_path(), self._lock_token, self._repo_lock_token,
2297
if response != ('ok',):
2298
raise errors.UnexpectedSmartServerResponse(response)
2299
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2302
def set_revision_history(self, rev_history):
2303
# Send just the tip revision of the history; the server will generate
2304
# the full history from that. If the revision doesn't exist in this
2305
# branch, NoSuchRevision will be raised.
2306
if rev_history == []:
2309
rev_id = rev_history[-1]
2310
self._set_last_revision(rev_id)
2311
for hook in branch.Branch.hooks['set_rh']:
2312
hook(self, rev_history)
2313
self._cache_revision_history(rev_history)
2315
def _get_parent_location(self):
2316
medium = self._client._medium
2317
if medium._is_remote_before((1, 13)):
2318
return self._vfs_get_parent_location()
2320
response = self._call('Branch.get_parent', self._remote_path())
2321
except errors.UnknownSmartMethod:
2322
medium._remember_remote_is_before((1, 13))
2323
return self._vfs_get_parent_location()
2324
if len(response) != 1:
2325
raise errors.UnexpectedSmartServerResponse(response)
2326
parent_location = response[0]
2327
if parent_location == '':
2329
return parent_location
2331
def _vfs_get_parent_location(self):
2333
return self._real_branch._get_parent_location()
2335
def _set_parent_location(self, url):
2336
medium = self._client._medium
2337
if medium._is_remote_before((1, 15)):
2338
return self._vfs_set_parent_location(url)
2340
call_url = url or ''
2341
if type(call_url) is not str:
2342
raise AssertionError('url must be a str or None (%s)' % url)
2343
response = self._call('Branch.set_parent_location',
2344
self._remote_path(), self._lock_token, self._repo_lock_token,
2346
except errors.UnknownSmartMethod:
2347
medium._remember_remote_is_before((1, 15))
2348
return self._vfs_set_parent_location(url)
2350
raise errors.UnexpectedSmartServerResponse(response)
2352
def _vfs_set_parent_location(self, url):
2354
return self._real_branch._set_parent_location(url)
2357
def pull(self, source, overwrite=False, stop_revision=None,
2359
self._clear_cached_state_of_remote_branch_only()
2361
return self._real_branch.pull(
2362
source, overwrite=overwrite, stop_revision=stop_revision,
2363
_override_hook_target=self, **kwargs)
2366
def push(self, target, overwrite=False, stop_revision=None):
2368
return self._real_branch.push(
2369
target, overwrite=overwrite, stop_revision=stop_revision,
2370
_override_hook_source_branch=self)
2372
def is_locked(self):
2373
return self._lock_count >= 1
2376
def revision_id_to_revno(self, revision_id):
2378
return self._real_branch.revision_id_to_revno(revision_id)
2381
def set_last_revision_info(self, revno, revision_id):
2382
# XXX: These should be returned by the set_last_revision_info verb
2383
old_revno, old_revid = self.last_revision_info()
2384
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2385
revision_id = ensure_null(revision_id)
2387
response = self._call('Branch.set_last_revision_info',
2388
self._remote_path(), self._lock_token, self._repo_lock_token,
2389
str(revno), revision_id)
2390
except errors.UnknownSmartMethod:
2392
self._clear_cached_state_of_remote_branch_only()
2393
self._real_branch.set_last_revision_info(revno, revision_id)
2394
self._last_revision_info_cache = revno, revision_id
2396
if response == ('ok',):
2397
self._clear_cached_state()
2398
self._last_revision_info_cache = revno, revision_id
2399
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2400
# Update the _real_branch's cache too.
2401
if self._real_branch is not None:
2402
cache = self._last_revision_info_cache
2403
self._real_branch._last_revision_info_cache = cache
2405
raise errors.UnexpectedSmartServerResponse(response)
2408
def generate_revision_history(self, revision_id, last_rev=None,
2410
medium = self._client._medium
2411
if not medium._is_remote_before((1, 6)):
2412
# Use a smart method for 1.6 and above servers
2414
self._set_last_revision_descendant(revision_id, other_branch,
2415
allow_diverged=True, allow_overwrite_descendant=True)
2417
except errors.UnknownSmartMethod:
2418
medium._remember_remote_is_before((1, 6))
2419
self._clear_cached_state_of_remote_branch_only()
2420
self.set_revision_history(self._lefthand_history(revision_id,
2421
last_rev=last_rev,other_branch=other_branch))
2423
def set_push_location(self, location):
2425
return self._real_branch.set_push_location(location)
2428
class RemoteConfig(object):
2429
"""A Config that reads and writes from smart verbs.
2431
It is a low-level object that considers config data to be name/value pairs
2432
that may be associated with a section. Assigning meaning to the these
2433
values is done at higher levels like bzrlib.config.TreeConfig.
2436
def get_option(self, name, section=None, default=None):
2437
"""Return the value associated with a named option.
2439
:param name: The name of the value
2440
:param section: The section the option is in (if any)
2441
:param default: The value to return if the value is not set
2442
:return: The value or default value
2445
configobj = self._get_configobj()
2447
section_obj = configobj
2450
section_obj = configobj[section]
2453
return section_obj.get(name, default)
2454
except errors.UnknownSmartMethod:
2455
return self._vfs_get_option(name, section, default)
2457
def _response_to_configobj(self, response):
2458
if len(response[0]) and response[0][0] != 'ok':
2459
raise errors.UnexpectedSmartServerResponse(response)
2460
lines = response[1].read_body_bytes().splitlines()
2461
return config.ConfigObj(lines, encoding='utf-8')
2464
class RemoteBranchConfig(RemoteConfig):
2465
"""A RemoteConfig for Branches."""
2467
def __init__(self, branch):
2468
self._branch = branch
2470
def _get_configobj(self):
2471
path = self._branch._remote_path()
2472
response = self._branch._client.call_expecting_body(
2473
'Branch.get_config_file', path)
2474
return self._response_to_configobj(response)
2476
def set_option(self, value, name, section=None):
2477
"""Set the value associated with a named option.
2479
:param value: The value to set
2480
:param name: The name of the value to set
2481
:param section: The section the option is in (if any)
2483
medium = self._branch._client._medium
2484
if medium._is_remote_before((1, 14)):
2485
return self._vfs_set_option(value, name, section)
2487
path = self._branch._remote_path()
2488
response = self._branch._client.call('Branch.set_config_option',
2489
path, self._branch._lock_token, self._branch._repo_lock_token,
2490
value.encode('utf8'), name, section or '')
2491
except errors.UnknownSmartMethod:
2492
medium._remember_remote_is_before((1, 14))
2493
return self._vfs_set_option(value, name, section)
2495
raise errors.UnexpectedSmartServerResponse(response)
2497
def _real_object(self):
2498
self._branch._ensure_real()
2499
return self._branch._real_branch
2501
def _vfs_set_option(self, value, name, section=None):
2502
return self._real_object()._get_config().set_option(
2503
value, name, section)
2506
class RemoteBzrDirConfig(RemoteConfig):
2507
"""A RemoteConfig for BzrDirs."""
2509
def __init__(self, bzrdir):
2510
self._bzrdir = bzrdir
2512
def _get_configobj(self):
2513
medium = self._bzrdir._client._medium
2514
verb = 'BzrDir.get_config_file'
2515
if medium._is_remote_before((1, 15)):
2516
raise errors.UnknownSmartMethod(verb)
2517
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2518
response = self._bzrdir._call_expecting_body(
2520
return self._response_to_configobj(response)
2522
def _vfs_get_option(self, name, section, default):
2523
return self._real_object()._get_config().get_option(
2524
name, section, default)
2526
def set_option(self, value, name, section=None):
2527
"""Set the value associated with a named option.
2529
:param value: The value to set
2530
:param name: The name of the value to set
2531
:param section: The section the option is in (if any)
2533
return self._real_object()._get_config().set_option(
2534
value, name, section)
2536
def _real_object(self):
2537
self._bzrdir._ensure_real()
2538
return self._bzrdir._real_bzrdir
2542
def _extract_tar(tar, to_dir):
2543
"""Extract all the contents of a tarfile object.
2545
A replacement for extractall, which is not present in python2.4
2548
tar.extract(tarinfo, to_dir)
2551
def _translate_error(err, **context):
2552
"""Translate an ErrorFromSmartServer into a more useful error.
2554
Possible context keys:
2562
If the error from the server doesn't match a known pattern, then
2563
UnknownErrorFromSmartServer is raised.
2567
return context[name]
2568
except KeyError, key_err:
2569
mutter('Missing key %r in context %r', key_err.args[0], context)
2572
"""Get the path from the context if present, otherwise use first error
2576
return context['path']
2577
except KeyError, key_err:
2579
return err.error_args[0]
2580
except IndexError, idx_err:
2582
'Missing key %r in context %r', key_err.args[0], context)
2585
if err.error_verb == 'NoSuchRevision':
2586
raise NoSuchRevision(find('branch'), err.error_args[0])
2587
elif err.error_verb == 'nosuchrevision':
2588
raise NoSuchRevision(find('repository'), err.error_args[0])
2589
elif err.error_tuple == ('nobranch',):
2590
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2591
elif err.error_verb == 'norepository':
2592
raise errors.NoRepositoryPresent(find('bzrdir'))
2593
elif err.error_verb == 'LockContention':
2594
raise errors.LockContention('(remote lock)')
2595
elif err.error_verb == 'UnlockableTransport':
2596
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2597
elif err.error_verb == 'LockFailed':
2598
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2599
elif err.error_verb == 'TokenMismatch':
2600
raise errors.TokenMismatch(find('token'), '(remote token)')
2601
elif err.error_verb == 'Diverged':
2602
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2603
elif err.error_verb == 'TipChangeRejected':
2604
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2605
elif err.error_verb == 'UnstackableBranchFormat':
2606
raise errors.UnstackableBranchFormat(*err.error_args)
2607
elif err.error_verb == 'UnstackableRepositoryFormat':
2608
raise errors.UnstackableRepositoryFormat(*err.error_args)
2609
elif err.error_verb == 'NotStacked':
2610
raise errors.NotStacked(branch=find('branch'))
2611
elif err.error_verb == 'PermissionDenied':
2613
if len(err.error_args) >= 2:
2614
extra = err.error_args[1]
2617
raise errors.PermissionDenied(path, extra=extra)
2618
elif err.error_verb == 'ReadError':
2620
raise errors.ReadError(path)
2621
elif err.error_verb == 'NoSuchFile':
2623
raise errors.NoSuchFile(path)
2624
elif err.error_verb == 'FileExists':
2625
raise errors.FileExists(err.error_args[0])
2626
elif err.error_verb == 'DirectoryNotEmpty':
2627
raise errors.DirectoryNotEmpty(err.error_args[0])
2628
elif err.error_verb == 'ShortReadvError':
2629
args = err.error_args
2630
raise errors.ShortReadvError(
2631
args[0], int(args[1]), int(args[2]), int(args[3]))
2632
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2633
encoding = str(err.error_args[0]) # encoding must always be a string
2634
val = err.error_args[1]
2635
start = int(err.error_args[2])
2636
end = int(err.error_args[3])
2637
reason = str(err.error_args[4]) # reason must always be a string
2638
if val.startswith('u:'):
2639
val = val[2:].decode('utf-8')
2640
elif val.startswith('s:'):
2641
val = val[2:].decode('base64')
2642
if err.error_verb == 'UnicodeDecodeError':
2643
raise UnicodeDecodeError(encoding, val, start, end, reason)
2644
elif err.error_verb == 'UnicodeEncodeError':
2645
raise UnicodeEncodeError(encoding, val, start, end, reason)
2646
elif err.error_verb == 'ReadOnlyError':
2647
raise errors.TransportNotPossible('readonly transport')
2648
raise errors.UnknownErrorFromSmartServer(err)