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']
1974
self._setup_stacking()
1976
def _setup_stacking(self):
1977
# configure stacking into the remote repository, by reading it from
1980
fallback_url = self.get_stacked_on_url()
1981
except (errors.NotStacked, errors.UnstackableBranchFormat,
1982
errors.UnstackableRepositoryFormat), e:
1984
self._activate_fallback_location(fallback_url)
1986
def _get_config(self):
1987
return RemoteBranchConfig(self)
1989
def _get_real_transport(self):
1990
# if we try vfs access, return the real branch's vfs transport
1992
return self._real_branch._transport
1994
_transport = property(_get_real_transport)
1997
return "%s(%s)" % (self.__class__.__name__, self.base)
2001
def _ensure_real(self):
2002
"""Ensure that there is a _real_branch set.
2004
Used before calls to self._real_branch.
2006
if self._real_branch is None:
2007
if not vfs.vfs_enabled():
2008
raise AssertionError('smart server vfs must be enabled '
2009
'to use vfs implementation')
2010
self.bzrdir._ensure_real()
2011
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
2012
if self.repository._real_repository is None:
2013
# Give the remote repository the matching real repo.
2014
real_repo = self._real_branch.repository
2015
if isinstance(real_repo, RemoteRepository):
2016
real_repo._ensure_real()
2017
real_repo = real_repo._real_repository
2018
self.repository._set_real_repository(real_repo)
2019
# Give the real branch the remote repository to let fast-pathing
2021
self._real_branch.repository = self.repository
2022
if self._lock_mode == 'r':
2023
self._real_branch.lock_read()
2024
elif self._lock_mode == 'w':
2025
self._real_branch.lock_write(token=self._lock_token)
2027
def _translate_error(self, err, **context):
2028
self.repository._translate_error(err, branch=self, **context)
2030
def _clear_cached_state(self):
2031
super(RemoteBranch, self)._clear_cached_state()
2032
if self._real_branch is not None:
2033
self._real_branch._clear_cached_state()
2035
def _clear_cached_state_of_remote_branch_only(self):
2036
"""Like _clear_cached_state, but doesn't clear the cache of
2039
This is useful when falling back to calling a method of
2040
self._real_branch that changes state. In that case the underlying
2041
branch changes, so we need to invalidate this RemoteBranch's cache of
2042
it. However, there's no need to invalidate the _real_branch's cache
2043
too, in fact doing so might harm performance.
2045
super(RemoteBranch, self)._clear_cached_state()
2048
def control_files(self):
2049
# Defer actually creating RemoteBranchLockableFiles until its needed,
2050
# because it triggers an _ensure_real that we otherwise might not need.
2051
if self._control_files is None:
2052
self._control_files = RemoteBranchLockableFiles(
2053
self.bzrdir, self._client)
2054
return self._control_files
2056
def _get_checkout_format(self):
2058
return self._real_branch._get_checkout_format()
2060
def get_physical_lock_status(self):
2061
"""See Branch.get_physical_lock_status()."""
2062
# should be an API call to the server, as branches must be lockable.
2064
return self._real_branch.get_physical_lock_status()
2066
def get_stacked_on_url(self):
2067
"""Get the URL this branch is stacked against.
2069
:raises NotStacked: If the branch is not stacked.
2070
:raises UnstackableBranchFormat: If the branch does not support
2072
:raises UnstackableRepositoryFormat: If the repository does not support
2076
# there may not be a repository yet, so we can't use
2077
# self._translate_error, so we can't use self._call either.
2078
response = self._client.call('Branch.get_stacked_on_url',
2079
self._remote_path())
2080
except errors.ErrorFromSmartServer, err:
2081
# there may not be a repository yet, so we can't call through
2082
# its _translate_error
2083
_translate_error(err, branch=self)
2084
except errors.UnknownSmartMethod, err:
2086
return self._real_branch.get_stacked_on_url()
2087
if response[0] != 'ok':
2088
raise errors.UnexpectedSmartServerResponse(response)
2091
def _vfs_get_tags_bytes(self):
2093
return self._real_branch._get_tags_bytes()
2095
def _get_tags_bytes(self):
2096
medium = self._client._medium
2097
if medium._is_remote_before((1, 13)):
2098
return self._vfs_get_tags_bytes()
2100
response = self._call('Branch.get_tags_bytes', self._remote_path())
2101
except errors.UnknownSmartMethod:
2102
medium._remember_remote_is_before((1, 13))
2103
return self._vfs_get_tags_bytes()
2106
def lock_read(self):
2107
self.repository.lock_read()
2108
if not self._lock_mode:
2109
self._lock_mode = 'r'
2110
self._lock_count = 1
2111
if self._real_branch is not None:
2112
self._real_branch.lock_read()
2114
self._lock_count += 1
2116
def _remote_lock_write(self, token):
2118
branch_token = repo_token = ''
2120
branch_token = token
2121
repo_token = self.repository.lock_write()
2122
self.repository.unlock()
2123
err_context = {'token': token}
2124
response = self._call(
2125
'Branch.lock_write', self._remote_path(), branch_token,
2126
repo_token or '', **err_context)
2127
if response[0] != 'ok':
2128
raise errors.UnexpectedSmartServerResponse(response)
2129
ok, branch_token, repo_token = response
2130
return branch_token, repo_token
2132
def lock_write(self, token=None):
2133
if not self._lock_mode:
2134
# Lock the branch and repo in one remote call.
2135
remote_tokens = self._remote_lock_write(token)
2136
self._lock_token, self._repo_lock_token = remote_tokens
2137
if not self._lock_token:
2138
raise SmartProtocolError('Remote server did not return a token!')
2139
# Tell the self.repository object that it is locked.
2140
self.repository.lock_write(
2141
self._repo_lock_token, _skip_rpc=True)
2143
if self._real_branch is not None:
2144
self._real_branch.lock_write(token=self._lock_token)
2145
if token is not None:
2146
self._leave_lock = True
2148
self._leave_lock = False
2149
self._lock_mode = 'w'
2150
self._lock_count = 1
2151
elif self._lock_mode == 'r':
2152
raise errors.ReadOnlyTransaction
2154
if token is not None:
2155
# A token was given to lock_write, and we're relocking, so
2156
# check that the given token actually matches the one we
2158
if token != self._lock_token:
2159
raise errors.TokenMismatch(token, self._lock_token)
2160
self._lock_count += 1
2161
# Re-lock the repository too.
2162
self.repository.lock_write(self._repo_lock_token)
2163
return self._lock_token or None
2165
def _set_tags_bytes(self, bytes):
2167
return self._real_branch._set_tags_bytes(bytes)
2169
def _unlock(self, branch_token, repo_token):
2170
err_context = {'token': str((branch_token, repo_token))}
2171
response = self._call(
2172
'Branch.unlock', self._remote_path(), branch_token,
2173
repo_token or '', **err_context)
2174
if response == ('ok',):
2176
raise errors.UnexpectedSmartServerResponse(response)
2180
self._lock_count -= 1
2181
if not self._lock_count:
2182
self._clear_cached_state()
2183
mode = self._lock_mode
2184
self._lock_mode = None
2185
if self._real_branch is not None:
2186
if (not self._leave_lock and mode == 'w' and
2187
self._repo_lock_token):
2188
# If this RemoteBranch will remove the physical lock
2189
# for the repository, make sure the _real_branch
2190
# doesn't do it first. (Because the _real_branch's
2191
# repository is set to be the RemoteRepository.)
2192
self._real_branch.repository.leave_lock_in_place()
2193
self._real_branch.unlock()
2195
# Only write-locked branched need to make a remote method
2196
# call to perform the unlock.
2198
if not self._lock_token:
2199
raise AssertionError('Locked, but no token!')
2200
branch_token = self._lock_token
2201
repo_token = self._repo_lock_token
2202
self._lock_token = None
2203
self._repo_lock_token = None
2204
if not self._leave_lock:
2205
self._unlock(branch_token, repo_token)
2207
self.repository.unlock()
2209
def break_lock(self):
2211
return self._real_branch.break_lock()
2213
def leave_lock_in_place(self):
2214
if not self._lock_token:
2215
raise NotImplementedError(self.leave_lock_in_place)
2216
self._leave_lock = True
2218
def dont_leave_lock_in_place(self):
2219
if not self._lock_token:
2220
raise NotImplementedError(self.dont_leave_lock_in_place)
2221
self._leave_lock = False
2223
def _last_revision_info(self):
2224
response = self._call('Branch.last_revision_info', self._remote_path())
2225
if response[0] != 'ok':
2226
raise SmartProtocolError('unexpected response code %s' % (response,))
2227
revno = int(response[1])
2228
last_revision = response[2]
2229
return (revno, last_revision)
2231
def _gen_revision_history(self):
2232
"""See Branch._gen_revision_history()."""
2233
response_tuple, response_handler = self._call_expecting_body(
2234
'Branch.revision_history', self._remote_path())
2235
if response_tuple[0] != 'ok':
2236
raise errors.UnexpectedSmartServerResponse(response_tuple)
2237
result = response_handler.read_body_bytes().split('\x00')
2242
def _remote_path(self):
2243
return self.bzrdir._path_for_remote_call(self._client)
2245
def _set_last_revision_descendant(self, revision_id, other_branch,
2246
allow_diverged=False, allow_overwrite_descendant=False):
2247
# This performs additional work to meet the hook contract; while its
2248
# undesirable, we have to synthesise the revno to call the hook, and
2249
# not calling the hook is worse as it means changes can't be prevented.
2250
# Having calculated this though, we can't just call into
2251
# set_last_revision_info as a simple call, because there is a set_rh
2252
# hook that some folk may still be using.
2253
old_revno, old_revid = self.last_revision_info()
2254
history = self._lefthand_history(revision_id)
2255
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2256
err_context = {'other_branch': other_branch}
2257
response = self._call('Branch.set_last_revision_ex',
2258
self._remote_path(), self._lock_token, self._repo_lock_token,
2259
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2261
self._clear_cached_state()
2262
if len(response) != 3 and response[0] != 'ok':
2263
raise errors.UnexpectedSmartServerResponse(response)
2264
new_revno, new_revision_id = response[1:]
2265
self._last_revision_info_cache = new_revno, new_revision_id
2266
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2267
if self._real_branch is not None:
2268
cache = new_revno, new_revision_id
2269
self._real_branch._last_revision_info_cache = cache
2271
def _set_last_revision(self, revision_id):
2272
old_revno, old_revid = self.last_revision_info()
2273
# This performs additional work to meet the hook contract; while its
2274
# undesirable, we have to synthesise the revno to call the hook, and
2275
# not calling the hook is worse as it means changes can't be prevented.
2276
# Having calculated this though, we can't just call into
2277
# set_last_revision_info as a simple call, because there is a set_rh
2278
# hook that some folk may still be using.
2279
history = self._lefthand_history(revision_id)
2280
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2281
self._clear_cached_state()
2282
response = self._call('Branch.set_last_revision',
2283
self._remote_path(), self._lock_token, self._repo_lock_token,
2285
if response != ('ok',):
2286
raise errors.UnexpectedSmartServerResponse(response)
2287
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2290
def set_revision_history(self, rev_history):
2291
# Send just the tip revision of the history; the server will generate
2292
# the full history from that. If the revision doesn't exist in this
2293
# branch, NoSuchRevision will be raised.
2294
if rev_history == []:
2297
rev_id = rev_history[-1]
2298
self._set_last_revision(rev_id)
2299
for hook in branch.Branch.hooks['set_rh']:
2300
hook(self, rev_history)
2301
self._cache_revision_history(rev_history)
2303
def _get_parent_location(self):
2304
medium = self._client._medium
2305
if medium._is_remote_before((1, 13)):
2306
return self._vfs_get_parent_location()
2308
response = self._call('Branch.get_parent', self._remote_path())
2309
except errors.UnknownSmartMethod:
2310
medium._remember_remote_is_before((1, 13))
2311
return self._vfs_get_parent_location()
2312
if len(response) != 1:
2313
raise errors.UnexpectedSmartServerResponse(response)
2314
parent_location = response[0]
2315
if parent_location == '':
2317
return parent_location
2319
def _vfs_get_parent_location(self):
2321
return self._real_branch._get_parent_location()
2323
def _set_parent_location(self, url):
2324
medium = self._client._medium
2325
if medium._is_remote_before((1, 15)):
2326
return self._vfs_set_parent_location(url)
2328
call_url = url or ''
2329
if type(call_url) is not str:
2330
raise AssertionError('url must be a str or None (%s)' % url)
2331
response = self._call('Branch.set_parent_location',
2332
self._remote_path(), self._lock_token, self._repo_lock_token,
2334
except errors.UnknownSmartMethod:
2335
medium._remember_remote_is_before((1, 15))
2336
return self._vfs_set_parent_location(url)
2338
raise errors.UnexpectedSmartServerResponse(response)
2340
def _vfs_set_parent_location(self, url):
2342
return self._real_branch._set_parent_location(url)
2345
def pull(self, source, overwrite=False, stop_revision=None,
2347
self._clear_cached_state_of_remote_branch_only()
2349
return self._real_branch.pull(
2350
source, overwrite=overwrite, stop_revision=stop_revision,
2351
_override_hook_target=self, **kwargs)
2354
def push(self, target, overwrite=False, stop_revision=None):
2356
return self._real_branch.push(
2357
target, overwrite=overwrite, stop_revision=stop_revision,
2358
_override_hook_source_branch=self)
2360
def is_locked(self):
2361
return self._lock_count >= 1
2364
def revision_id_to_revno(self, revision_id):
2366
return self._real_branch.revision_id_to_revno(revision_id)
2369
def set_last_revision_info(self, revno, revision_id):
2370
# XXX: These should be returned by the set_last_revision_info verb
2371
old_revno, old_revid = self.last_revision_info()
2372
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2373
revision_id = ensure_null(revision_id)
2375
response = self._call('Branch.set_last_revision_info',
2376
self._remote_path(), self._lock_token, self._repo_lock_token,
2377
str(revno), revision_id)
2378
except errors.UnknownSmartMethod:
2380
self._clear_cached_state_of_remote_branch_only()
2381
self._real_branch.set_last_revision_info(revno, revision_id)
2382
self._last_revision_info_cache = revno, revision_id
2384
if response == ('ok',):
2385
self._clear_cached_state()
2386
self._last_revision_info_cache = revno, revision_id
2387
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2388
# Update the _real_branch's cache too.
2389
if self._real_branch is not None:
2390
cache = self._last_revision_info_cache
2391
self._real_branch._last_revision_info_cache = cache
2393
raise errors.UnexpectedSmartServerResponse(response)
2396
def generate_revision_history(self, revision_id, last_rev=None,
2398
medium = self._client._medium
2399
if not medium._is_remote_before((1, 6)):
2400
# Use a smart method for 1.6 and above servers
2402
self._set_last_revision_descendant(revision_id, other_branch,
2403
allow_diverged=True, allow_overwrite_descendant=True)
2405
except errors.UnknownSmartMethod:
2406
medium._remember_remote_is_before((1, 6))
2407
self._clear_cached_state_of_remote_branch_only()
2408
self.set_revision_history(self._lefthand_history(revision_id,
2409
last_rev=last_rev,other_branch=other_branch))
2411
def set_push_location(self, location):
2413
return self._real_branch.set_push_location(location)
2416
class RemoteConfig(object):
2417
"""A Config that reads and writes from smart verbs.
2419
It is a low-level object that considers config data to be name/value pairs
2420
that may be associated with a section. Assigning meaning to the these
2421
values is done at higher levels like bzrlib.config.TreeConfig.
2424
def get_option(self, name, section=None, default=None):
2425
"""Return the value associated with a named option.
2427
:param name: The name of the value
2428
:param section: The section the option is in (if any)
2429
:param default: The value to return if the value is not set
2430
:return: The value or default value
2433
configobj = self._get_configobj()
2435
section_obj = configobj
2438
section_obj = configobj[section]
2441
return section_obj.get(name, default)
2442
except errors.UnknownSmartMethod:
2443
return self._vfs_get_option(name, section, default)
2445
def _response_to_configobj(self, response):
2446
if len(response[0]) and response[0][0] != 'ok':
2447
raise errors.UnexpectedSmartServerResponse(response)
2448
lines = response[1].read_body_bytes().splitlines()
2449
return config.ConfigObj(lines, encoding='utf-8')
2452
class RemoteBranchConfig(RemoteConfig):
2453
"""A RemoteConfig for Branches."""
2455
def __init__(self, branch):
2456
self._branch = branch
2458
def _get_configobj(self):
2459
path = self._branch._remote_path()
2460
response = self._branch._client.call_expecting_body(
2461
'Branch.get_config_file', path)
2462
return self._response_to_configobj(response)
2464
def set_option(self, value, name, section=None):
2465
"""Set the value associated with a named option.
2467
:param value: The value to set
2468
:param name: The name of the value to set
2469
:param section: The section the option is in (if any)
2471
medium = self._branch._client._medium
2472
if medium._is_remote_before((1, 14)):
2473
return self._vfs_set_option(value, name, section)
2475
path = self._branch._remote_path()
2476
response = self._branch._client.call('Branch.set_config_option',
2477
path, self._branch._lock_token, self._branch._repo_lock_token,
2478
value.encode('utf8'), name, section or '')
2479
except errors.UnknownSmartMethod:
2480
medium._remember_remote_is_before((1, 14))
2481
return self._vfs_set_option(value, name, section)
2483
raise errors.UnexpectedSmartServerResponse(response)
2485
def _real_object(self):
2486
self._branch._ensure_real()
2487
return self._branch._real_branch
2489
def _vfs_set_option(self, value, name, section=None):
2490
return self._real_object()._get_config().set_option(
2491
value, name, section)
2494
class RemoteBzrDirConfig(RemoteConfig):
2495
"""A RemoteConfig for BzrDirs."""
2497
def __init__(self, bzrdir):
2498
self._bzrdir = bzrdir
2500
def _get_configobj(self):
2501
medium = self._bzrdir._client._medium
2502
verb = 'BzrDir.get_config_file'
2503
if medium._is_remote_before((1, 15)):
2504
raise errors.UnknownSmartMethod(verb)
2505
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2506
response = self._bzrdir._call_expecting_body(
2508
return self._response_to_configobj(response)
2510
def _vfs_get_option(self, name, section, default):
2511
return self._real_object()._get_config().get_option(
2512
name, section, default)
2514
def set_option(self, value, name, section=None):
2515
"""Set the value associated with a named option.
2517
:param value: The value to set
2518
:param name: The name of the value to set
2519
:param section: The section the option is in (if any)
2521
return self._real_object()._get_config().set_option(
2522
value, name, section)
2524
def _real_object(self):
2525
self._bzrdir._ensure_real()
2526
return self._bzrdir._real_bzrdir
2530
def _extract_tar(tar, to_dir):
2531
"""Extract all the contents of a tarfile object.
2533
A replacement for extractall, which is not present in python2.4
2536
tar.extract(tarinfo, to_dir)
2539
def _translate_error(err, **context):
2540
"""Translate an ErrorFromSmartServer into a more useful error.
2542
Possible context keys:
2550
If the error from the server doesn't match a known pattern, then
2551
UnknownErrorFromSmartServer is raised.
2555
return context[name]
2556
except KeyError, key_err:
2557
mutter('Missing key %r in context %r', key_err.args[0], context)
2560
"""Get the path from the context if present, otherwise use first error
2564
return context['path']
2565
except KeyError, key_err:
2567
return err.error_args[0]
2568
except IndexError, idx_err:
2570
'Missing key %r in context %r', key_err.args[0], context)
2573
if err.error_verb == 'NoSuchRevision':
2574
raise NoSuchRevision(find('branch'), err.error_args[0])
2575
elif err.error_verb == 'nosuchrevision':
2576
raise NoSuchRevision(find('repository'), err.error_args[0])
2577
elif err.error_tuple == ('nobranch',):
2578
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2579
elif err.error_verb == 'norepository':
2580
raise errors.NoRepositoryPresent(find('bzrdir'))
2581
elif err.error_verb == 'LockContention':
2582
raise errors.LockContention('(remote lock)')
2583
elif err.error_verb == 'UnlockableTransport':
2584
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2585
elif err.error_verb == 'LockFailed':
2586
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2587
elif err.error_verb == 'TokenMismatch':
2588
raise errors.TokenMismatch(find('token'), '(remote token)')
2589
elif err.error_verb == 'Diverged':
2590
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2591
elif err.error_verb == 'TipChangeRejected':
2592
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2593
elif err.error_verb == 'UnstackableBranchFormat':
2594
raise errors.UnstackableBranchFormat(*err.error_args)
2595
elif err.error_verb == 'UnstackableRepositoryFormat':
2596
raise errors.UnstackableRepositoryFormat(*err.error_args)
2597
elif err.error_verb == 'NotStacked':
2598
raise errors.NotStacked(branch=find('branch'))
2599
elif err.error_verb == 'PermissionDenied':
2601
if len(err.error_args) >= 2:
2602
extra = err.error_args[1]
2605
raise errors.PermissionDenied(path, extra=extra)
2606
elif err.error_verb == 'ReadError':
2608
raise errors.ReadError(path)
2609
elif err.error_verb == 'NoSuchFile':
2611
raise errors.NoSuchFile(path)
2612
elif err.error_verb == 'FileExists':
2613
raise errors.FileExists(err.error_args[0])
2614
elif err.error_verb == 'DirectoryNotEmpty':
2615
raise errors.DirectoryNotEmpty(err.error_args[0])
2616
elif err.error_verb == 'ShortReadvError':
2617
args = err.error_args
2618
raise errors.ShortReadvError(
2619
args[0], int(args[1]), int(args[2]), int(args[3]))
2620
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2621
encoding = str(err.error_args[0]) # encoding must always be a string
2622
val = err.error_args[1]
2623
start = int(err.error_args[2])
2624
end = int(err.error_args[3])
2625
reason = str(err.error_args[4]) # reason must always be a string
2626
if val.startswith('u:'):
2627
val = val[2:].decode('utf-8')
2628
elif val.startswith('s:'):
2629
val = val[2:].decode('base64')
2630
if err.error_verb == 'UnicodeDecodeError':
2631
raise UnicodeDecodeError(encoding, val, start, end, reason)
2632
elif err.error_verb == 'UnicodeEncodeError':
2633
raise UnicodeEncodeError(encoding, val, start, end, reason)
2634
elif err.error_verb == 'ReadOnlyError':
2635
raise errors.TransportNotPossible('readonly transport')
2636
raise errors.UnknownErrorFromSmartServer(err)