1
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
33
revision as _mod_revision,
36
from bzrlib.branch import BranchReferenceFormat
37
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
38
from bzrlib.decorators import needs_read_lock, needs_write_lock
39
from bzrlib.errors import (
43
from bzrlib.lockable_files import LockableFiles
44
from bzrlib.smart import client, vfs, repository as smart_repo
45
from bzrlib.revision import ensure_null, NULL_REVISION
46
from bzrlib.trace import mutter, note, warning
49
class _RpcHelper(object):
50
"""Mixin class that helps with issuing RPCs."""
52
def _call(self, method, *args, **err_context):
54
return self._client.call(method, *args)
55
except errors.ErrorFromSmartServer, err:
56
self._translate_error(err, **err_context)
58
def _call_expecting_body(self, method, *args, **err_context):
60
return self._client.call_expecting_body(method, *args)
61
except errors.ErrorFromSmartServer, err:
62
self._translate_error(err, **err_context)
64
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
67
return self._client.call_with_body_bytes_expecting_body(
68
method, args, body_bytes)
69
except errors.ErrorFromSmartServer, err:
70
self._translate_error(err, **err_context)
73
def response_tuple_to_repo_format(response):
74
"""Convert a response tuple describing a repository format to a format."""
75
format = RemoteRepositoryFormat()
76
format._rich_root_data = (response[0] == 'yes')
77
format._supports_tree_reference = (response[1] == 'yes')
78
format._supports_external_lookups = (response[2] == 'yes')
79
format._network_name = response[3]
83
# Note: RemoteBzrDirFormat is in bzrdir.py
85
class RemoteBzrDir(BzrDir, _RpcHelper):
86
"""Control directory on a remote server, accessed via bzr:// or similar."""
88
def __init__(self, transport, format, _client=None):
89
"""Construct a RemoteBzrDir.
91
:param _client: Private parameter for testing. Disables probing and the
94
BzrDir.__init__(self, transport, format)
95
# this object holds a delegated bzrdir that uses file-level operations
96
# to talk to the other side
97
self._real_bzrdir = None
98
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
99
# create_branch for details.
100
self._next_open_branch_result = None
103
medium = transport.get_smart_medium()
104
self._client = client._SmartClient(medium)
106
self._client = _client
109
path = self._path_for_remote_call(self._client)
110
response = self._call('BzrDir.open', path)
111
if response not in [('yes',), ('no',)]:
112
raise errors.UnexpectedSmartServerResponse(response)
113
if response == ('no',):
114
raise errors.NotBranchError(path=transport.base)
116
def _ensure_real(self):
117
"""Ensure that there is a _real_bzrdir set.
119
Used before calls to self._real_bzrdir.
121
if not self._real_bzrdir:
122
self._real_bzrdir = BzrDir.open_from_transport(
123
self.root_transport, _server_formats=False)
124
self._format._network_name = \
125
self._real_bzrdir._format.network_name()
127
def _translate_error(self, err, **context):
128
_translate_error(err, bzrdir=self, **context)
130
def break_lock(self):
131
# Prevent aliasing problems in the next_open_branch_result cache.
132
# See create_branch for rationale.
133
self._next_open_branch_result = None
134
return BzrDir.break_lock(self)
136
def _vfs_cloning_metadir(self, require_stacking=False):
138
return self._real_bzrdir.cloning_metadir(
139
require_stacking=require_stacking)
141
def cloning_metadir(self, require_stacking=False):
142
medium = self._client._medium
143
if medium._is_remote_before((1, 13)):
144
return self._vfs_cloning_metadir(require_stacking=require_stacking)
145
verb = 'BzrDir.cloning_metadir'
150
path = self._path_for_remote_call(self._client)
152
response = self._call(verb, path, stacking)
153
except errors.UnknownSmartMethod:
154
medium._remember_remote_is_before((1, 13))
155
return self._vfs_cloning_metadir(require_stacking=require_stacking)
156
except errors.UnknownErrorFromSmartServer, err:
157
if err.error_tuple != ('BranchReference',):
159
# We need to resolve the branch reference to determine the
160
# cloning_metadir. This causes unnecessary RPCs to open the
161
# referenced branch (and bzrdir, etc) but only when the caller
162
# didn't already resolve the branch reference.
163
referenced_branch = self.open_branch()
164
return referenced_branch.bzrdir.cloning_metadir()
165
if len(response) != 3:
166
raise errors.UnexpectedSmartServerResponse(response)
167
control_name, repo_name, branch_info = response
168
if len(branch_info) != 2:
169
raise errors.UnexpectedSmartServerResponse(response)
170
branch_ref, branch_name = branch_info
171
format = bzrdir.network_format_registry.get(control_name)
173
format.repository_format = repository.network_format_registry.get(
175
if branch_ref == 'ref':
176
# XXX: we need possible_transports here to avoid reopening the
177
# connection to the referenced location
178
ref_bzrdir = BzrDir.open(branch_name)
179
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
180
format.set_branch_format(branch_format)
181
elif branch_ref == 'branch':
183
format.set_branch_format(
184
branch.network_format_registry.get(branch_name))
186
raise errors.UnexpectedSmartServerResponse(response)
189
def create_repository(self, shared=False):
190
# as per meta1 formats - just delegate to the format object which may
192
result = self._format.repository_format.initialize(self, shared)
193
if not isinstance(result, RemoteRepository):
194
return self.open_repository()
198
def destroy_repository(self):
199
"""See BzrDir.destroy_repository"""
201
self._real_bzrdir.destroy_repository()
203
def create_branch(self):
204
# as per meta1 formats - just delegate to the format object which may
206
real_branch = self._format.get_branch_format().initialize(self)
207
if not isinstance(real_branch, RemoteBranch):
208
result = RemoteBranch(self, self.find_repository(), real_branch)
211
# BzrDir.clone_on_transport() uses the result of create_branch but does
212
# not return it to its callers; we save approximately 8% of our round
213
# trips by handing the branch we created back to the first caller to
214
# open_branch rather than probing anew. Long term we need a API in
215
# bzrdir that doesn't discard result objects (like result_branch).
217
self._next_open_branch_result = result
220
def destroy_branch(self):
221
"""See BzrDir.destroy_branch"""
223
self._real_bzrdir.destroy_branch()
224
self._next_open_branch_result = None
226
def create_workingtree(self, revision_id=None, from_branch=None):
227
raise errors.NotLocalUrl(self.transport.base)
229
def find_branch_format(self):
230
"""Find the branch 'format' for this bzrdir.
232
This might be a synthetic object for e.g. RemoteBranch and SVN.
234
b = self.open_branch()
237
def get_branch_reference(self):
238
"""See BzrDir.get_branch_reference()."""
239
response = self._get_branch_reference()
240
if response[0] == 'ref':
245
def _get_branch_reference(self):
246
path = self._path_for_remote_call(self._client)
247
medium = self._client._medium
248
if not medium._is_remote_before((1, 13)):
250
response = self._call('BzrDir.open_branchV2', path)
251
if response[0] not in ('ref', 'branch'):
252
raise errors.UnexpectedSmartServerResponse(response)
254
except errors.UnknownSmartMethod:
255
medium._remember_remote_is_before((1, 13))
256
response = self._call('BzrDir.open_branch', path)
257
if response[0] != 'ok':
258
raise errors.UnexpectedSmartServerResponse(response)
259
if response[1] != '':
260
return ('ref', response[1])
262
return ('branch', '')
264
def _get_tree_branch(self):
265
"""See BzrDir._get_tree_branch()."""
266
return None, self.open_branch()
268
def open_branch(self, _unsupported=False, ignore_fallbacks=False):
270
raise NotImplementedError('unsupported flag support not implemented yet.')
271
if self._next_open_branch_result is not None:
272
# See create_branch for details.
273
result = self._next_open_branch_result
274
self._next_open_branch_result = None
276
response = self._get_branch_reference()
277
if response[0] == 'ref':
278
# a branch reference, use the existing BranchReference logic.
279
format = BranchReferenceFormat()
280
return format.open(self, _found=True, location=response[1],
281
ignore_fallbacks=ignore_fallbacks)
282
branch_format_name = response[1]
283
if not branch_format_name:
284
branch_format_name = None
285
format = RemoteBranchFormat(network_name=branch_format_name)
286
return RemoteBranch(self, self.find_repository(), format=format,
287
setup_stacking=not ignore_fallbacks)
289
def _open_repo_v1(self, path):
290
verb = 'BzrDir.find_repository'
291
response = self._call(verb, path)
292
if response[0] != 'ok':
293
raise errors.UnexpectedSmartServerResponse(response)
294
# servers that only support the v1 method don't support external
297
repo = self._real_bzrdir.open_repository()
298
response = response + ('no', repo._format.network_name())
299
return response, repo
301
def _open_repo_v2(self, path):
302
verb = 'BzrDir.find_repositoryV2'
303
response = self._call(verb, path)
304
if response[0] != 'ok':
305
raise errors.UnexpectedSmartServerResponse(response)
307
repo = self._real_bzrdir.open_repository()
308
response = response + (repo._format.network_name(),)
309
return response, repo
311
def _open_repo_v3(self, path):
312
verb = 'BzrDir.find_repositoryV3'
313
medium = self._client._medium
314
if medium._is_remote_before((1, 13)):
315
raise errors.UnknownSmartMethod(verb)
317
response = self._call(verb, path)
318
except errors.UnknownSmartMethod:
319
medium._remember_remote_is_before((1, 13))
321
if response[0] != 'ok':
322
raise errors.UnexpectedSmartServerResponse(response)
323
return response, None
325
def open_repository(self):
326
path = self._path_for_remote_call(self._client)
328
for probe in [self._open_repo_v3, self._open_repo_v2,
331
response, real_repo = probe(path)
333
except errors.UnknownSmartMethod:
336
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
337
if response[0] != 'ok':
338
raise errors.UnexpectedSmartServerResponse(response)
339
if len(response) != 6:
340
raise SmartProtocolError('incorrect response length %s' % (response,))
341
if response[1] == '':
342
# repo is at this dir.
343
format = response_tuple_to_repo_format(response[2:])
344
# Used to support creating a real format instance when needed.
345
format._creating_bzrdir = self
346
remote_repo = RemoteRepository(self, format)
347
format._creating_repo = remote_repo
348
if real_repo is not None:
349
remote_repo._set_real_repository(real_repo)
352
raise errors.NoRepositoryPresent(self)
354
def open_workingtree(self, recommend_upgrade=True):
356
if self._real_bzrdir.has_workingtree():
357
raise errors.NotLocalUrl(self.root_transport)
359
raise errors.NoWorkingTree(self.root_transport.base)
361
def _path_for_remote_call(self, client):
362
"""Return the path to be used for this bzrdir in a remote call."""
363
return client.remote_path_from_transport(self.root_transport)
365
def get_branch_transport(self, branch_format):
367
return self._real_bzrdir.get_branch_transport(branch_format)
369
def get_repository_transport(self, repository_format):
371
return self._real_bzrdir.get_repository_transport(repository_format)
373
def get_workingtree_transport(self, workingtree_format):
375
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
377
def can_convert_format(self):
378
"""Upgrading of remote bzrdirs is not supported yet."""
381
def needs_format_conversion(self, format=None):
382
"""Upgrading of remote bzrdirs is not supported yet."""
384
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
385
% 'needs_format_conversion(format=None)')
388
def clone(self, url, revision_id=None, force_new_repo=False,
389
preserve_stacking=False):
391
return self._real_bzrdir.clone(url, revision_id=revision_id,
392
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
394
def _get_config(self):
395
return RemoteBzrDirConfig(self)
398
class RemoteRepositoryFormat(repository.RepositoryFormat):
399
"""Format for repositories accessed over a _SmartClient.
401
Instances of this repository are represented by RemoteRepository
404
The RemoteRepositoryFormat is parameterized during construction
405
to reflect the capabilities of the real, remote format. Specifically
406
the attributes rich_root_data and supports_tree_reference are set
407
on a per instance basis, and are not set (and should not be) at
410
:ivar _custom_format: If set, a specific concrete repository format that
411
will be used when initializing a repository with this
412
RemoteRepositoryFormat.
413
:ivar _creating_repo: If set, the repository object that this
414
RemoteRepositoryFormat was created for: it can be called into
415
to obtain data like the network name.
418
_matchingbzrdir = RemoteBzrDirFormat()
421
repository.RepositoryFormat.__init__(self)
422
self._custom_format = None
423
self._network_name = None
424
self._creating_bzrdir = None
425
self._supports_external_lookups = None
426
self._supports_tree_reference = None
427
self._rich_root_data = None
430
def fast_deltas(self):
432
return self._custom_format.fast_deltas
435
def rich_root_data(self):
436
if self._rich_root_data is None:
438
self._rich_root_data = self._custom_format.rich_root_data
439
return self._rich_root_data
442
def supports_external_lookups(self):
443
if self._supports_external_lookups is None:
445
self._supports_external_lookups = \
446
self._custom_format.supports_external_lookups
447
return self._supports_external_lookups
450
def supports_tree_reference(self):
451
if self._supports_tree_reference is None:
453
self._supports_tree_reference = \
454
self._custom_format.supports_tree_reference
455
return self._supports_tree_reference
457
def _vfs_initialize(self, a_bzrdir, shared):
458
"""Helper for common code in initialize."""
459
if self._custom_format:
460
# Custom format requested
461
result = self._custom_format.initialize(a_bzrdir, shared=shared)
462
elif self._creating_bzrdir is not None:
463
# Use the format that the repository we were created to back
465
prior_repo = self._creating_bzrdir.open_repository()
466
prior_repo._ensure_real()
467
result = prior_repo._real_repository._format.initialize(
468
a_bzrdir, shared=shared)
470
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
471
# support remote initialization.
472
# We delegate to a real object at this point (as RemoteBzrDir
473
# delegate to the repository format which would lead to infinite
474
# recursion if we just called a_bzrdir.create_repository.
475
a_bzrdir._ensure_real()
476
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
477
if not isinstance(result, RemoteRepository):
478
return self.open(a_bzrdir)
482
def initialize(self, a_bzrdir, shared=False):
483
# Being asked to create on a non RemoteBzrDir:
484
if not isinstance(a_bzrdir, RemoteBzrDir):
485
return self._vfs_initialize(a_bzrdir, shared)
486
medium = a_bzrdir._client._medium
487
if medium._is_remote_before((1, 13)):
488
return self._vfs_initialize(a_bzrdir, shared)
489
# Creating on a remote bzr dir.
490
# 1) get the network name to use.
491
if self._custom_format:
492
network_name = self._custom_format.network_name()
493
elif self._network_name:
494
network_name = self._network_name
496
# Select the current bzrlib default and ask for that.
497
reference_bzrdir_format = bzrdir.format_registry.get('default')()
498
reference_format = reference_bzrdir_format.repository_format
499
network_name = reference_format.network_name()
500
# 2) try direct creation via RPC
501
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
502
verb = 'BzrDir.create_repository'
508
response = a_bzrdir._call(verb, path, network_name, shared_str)
509
except errors.UnknownSmartMethod:
510
# Fallback - use vfs methods
511
medium._remember_remote_is_before((1, 13))
512
return self._vfs_initialize(a_bzrdir, shared)
514
# Turn the response into a RemoteRepository object.
515
format = response_tuple_to_repo_format(response[1:])
516
# Used to support creating a real format instance when needed.
517
format._creating_bzrdir = a_bzrdir
518
remote_repo = RemoteRepository(a_bzrdir, format)
519
format._creating_repo = remote_repo
522
def open(self, a_bzrdir):
523
if not isinstance(a_bzrdir, RemoteBzrDir):
524
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
525
return a_bzrdir.open_repository()
527
def _ensure_real(self):
528
if self._custom_format is None:
529
self._custom_format = repository.network_format_registry.get(
533
def _fetch_order(self):
535
return self._custom_format._fetch_order
538
def _fetch_uses_deltas(self):
540
return self._custom_format._fetch_uses_deltas
543
def _fetch_reconcile(self):
545
return self._custom_format._fetch_reconcile
547
def get_format_description(self):
548
return 'bzr remote repository'
550
def __eq__(self, other):
551
return self.__class__ is other.__class__
553
def check_conversion_target(self, target_format):
554
if self.rich_root_data and not target_format.rich_root_data:
555
raise errors.BadConversionTarget(
556
'Does not support rich root data.', target_format)
557
if (self.supports_tree_reference and
558
not getattr(target_format, 'supports_tree_reference', False)):
559
raise errors.BadConversionTarget(
560
'Does not support nested trees', target_format)
562
def network_name(self):
563
if self._network_name:
564
return self._network_name
565
self._creating_repo._ensure_real()
566
return self._creating_repo._real_repository._format.network_name()
569
def _serializer(self):
571
return self._custom_format._serializer
574
class RemoteRepository(_RpcHelper):
575
"""Repository accessed over rpc.
577
For the moment most operations are performed using local transport-backed
581
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
582
"""Create a RemoteRepository instance.
584
:param remote_bzrdir: The bzrdir hosting this repository.
585
:param format: The RemoteFormat object to use.
586
:param real_repository: If not None, a local implementation of the
587
repository logic for the repository, usually accessing the data
589
:param _client: Private testing parameter - override the smart client
590
to be used by the repository.
593
self._real_repository = real_repository
595
self._real_repository = None
596
self.bzrdir = remote_bzrdir
598
self._client = remote_bzrdir._client
600
self._client = _client
601
self._format = format
602
self._lock_mode = None
603
self._lock_token = None
605
self._leave_lock = False
606
# Cache of revision parents; misses are cached during read locks, and
607
# write locks when no _real_repository has been set.
608
self._unstacked_provider = graph.CachingParentsProvider(
609
get_parent_map=self._get_parent_map_rpc)
610
self._unstacked_provider.disable_cache()
612
# These depend on the actual remote format, so force them off for
613
# maximum compatibility. XXX: In future these should depend on the
614
# remote repository instance, but this is irrelevant until we perform
615
# reconcile via an RPC call.
616
self._reconcile_does_inventory_gc = False
617
self._reconcile_fixes_text_parents = False
618
self._reconcile_backsup_inventory = False
619
self.base = self.bzrdir.transport.base
620
# Additional places to query for data.
621
self._fallback_repositories = []
624
return "%s(%s)" % (self.__class__.__name__, self.base)
628
def abort_write_group(self, suppress_errors=False):
629
"""Complete a write group on the decorated repository.
631
Smart methods perform operations in a single step so this API
632
is not really applicable except as a compatibility thunk
633
for older plugins that don't use e.g. the CommitBuilder
636
:param suppress_errors: see Repository.abort_write_group.
639
return self._real_repository.abort_write_group(
640
suppress_errors=suppress_errors)
644
"""Decorate the real repository for now.
646
In the long term a full blown network facility is needed to avoid
647
creating a real repository object locally.
650
return self._real_repository.chk_bytes
652
def commit_write_group(self):
653
"""Complete a write group on the decorated repository.
655
Smart methods perform operations in a single step so this API
656
is not really applicable except as a compatibility thunk
657
for older plugins that don't use e.g. the CommitBuilder
661
return self._real_repository.commit_write_group()
663
def resume_write_group(self, tokens):
665
return self._real_repository.resume_write_group(tokens)
667
def suspend_write_group(self):
669
return self._real_repository.suspend_write_group()
671
def get_missing_parent_inventories(self, check_for_missing_texts=True):
673
return self._real_repository.get_missing_parent_inventories(
674
check_for_missing_texts=check_for_missing_texts)
676
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
678
return self._real_repository.get_rev_id_for_revno(
681
def get_rev_id_for_revno(self, revno, known_pair):
682
"""See Repository.get_rev_id_for_revno."""
683
path = self.bzrdir._path_for_remote_call(self._client)
685
if self._client._medium._is_remote_before((1, 17)):
686
return self._get_rev_id_for_revno_vfs(revno, known_pair)
687
response = self._call(
688
'Repository.get_rev_id_for_revno', path, revno, known_pair)
689
except errors.UnknownSmartMethod:
690
self._client._medium._remember_remote_is_before((1, 17))
691
return self._get_rev_id_for_revno_vfs(revno, known_pair)
692
if response[0] == 'ok':
693
return True, response[1]
694
elif response[0] == 'history-incomplete':
695
known_pair = response[1:3]
696
for fallback in self._fallback_repositories:
697
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
702
# Not found in any fallbacks
703
return False, known_pair
705
raise errors.UnexpectedSmartServerResponse(response)
707
def _ensure_real(self):
708
"""Ensure that there is a _real_repository set.
710
Used before calls to self._real_repository.
712
Note that _ensure_real causes many roundtrips to the server which are
713
not desirable, and prevents the use of smart one-roundtrip RPC's to
714
perform complex operations (such as accessing parent data, streaming
715
revisions etc). Adding calls to _ensure_real should only be done when
716
bringing up new functionality, adding fallbacks for smart methods that
717
require a fallback path, and never to replace an existing smart method
718
invocation. If in doubt chat to the bzr network team.
720
if self._real_repository is None:
721
if 'hpss' in debug.debug_flags:
723
warning('VFS Repository access triggered\n%s',
724
''.join(traceback.format_stack()))
725
self._unstacked_provider.missing_keys.clear()
726
self.bzrdir._ensure_real()
727
self._set_real_repository(
728
self.bzrdir._real_bzrdir.open_repository())
730
def _translate_error(self, err, **context):
731
self.bzrdir._translate_error(err, repository=self, **context)
733
def find_text_key_references(self):
734
"""Find the text key references within the repository.
736
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
737
revision_ids. Each altered file-ids has the exact revision_ids that
738
altered it listed explicitly.
739
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
740
to whether they were referred to by the inventory of the
741
revision_id that they contain. The inventory texts from all present
742
revision ids are assessed to generate this report.
745
return self._real_repository.find_text_key_references()
747
def _generate_text_key_index(self):
748
"""Generate a new text key index for the repository.
750
This is an expensive function that will take considerable time to run.
752
:return: A dict mapping (file_id, revision_id) tuples to a list of
753
parents, also (file_id, revision_id) tuples.
756
return self._real_repository._generate_text_key_index()
758
def _get_revision_graph(self, revision_id):
759
"""Private method for using with old (< 1.2) servers to fallback."""
760
if revision_id is None:
762
elif revision.is_null(revision_id):
765
path = self.bzrdir._path_for_remote_call(self._client)
766
response = self._call_expecting_body(
767
'Repository.get_revision_graph', path, revision_id)
768
response_tuple, response_handler = response
769
if response_tuple[0] != 'ok':
770
raise errors.UnexpectedSmartServerResponse(response_tuple)
771
coded = response_handler.read_body_bytes()
773
# no revisions in this repository!
775
lines = coded.split('\n')
778
d = tuple(line.split())
779
revision_graph[d[0]] = d[1:]
781
return revision_graph
784
"""See Repository._get_sink()."""
785
return RemoteStreamSink(self)
787
def _get_source(self, to_format):
788
"""Return a source for streaming from this repository."""
789
return RemoteStreamSource(self, to_format)
792
def has_revision(self, revision_id):
793
"""True if this repository has a copy of the revision."""
794
# Copy of bzrlib.repository.Repository.has_revision
795
return revision_id in self.has_revisions((revision_id,))
798
def has_revisions(self, revision_ids):
799
"""Probe to find out the presence of multiple revisions.
801
:param revision_ids: An iterable of revision_ids.
802
:return: A set of the revision_ids that were present.
804
# Copy of bzrlib.repository.Repository.has_revisions
805
parent_map = self.get_parent_map(revision_ids)
806
result = set(parent_map)
807
if _mod_revision.NULL_REVISION in revision_ids:
808
result.add(_mod_revision.NULL_REVISION)
811
def has_same_location(self, other):
812
return (self.__class__ is other.__class__ and
813
self.bzrdir.transport.base == other.bzrdir.transport.base)
815
def get_graph(self, other_repository=None):
816
"""Return the graph for this repository format"""
817
parents_provider = self._make_parents_provider(other_repository)
818
return graph.Graph(parents_provider)
820
def gather_stats(self, revid=None, committers=None):
821
"""See Repository.gather_stats()."""
822
path = self.bzrdir._path_for_remote_call(self._client)
823
# revid can be None to indicate no revisions, not just NULL_REVISION
824
if revid is None or revision.is_null(revid):
828
if committers is None or not committers:
829
fmt_committers = 'no'
831
fmt_committers = 'yes'
832
response_tuple, response_handler = self._call_expecting_body(
833
'Repository.gather_stats', path, fmt_revid, fmt_committers)
834
if response_tuple[0] != 'ok':
835
raise errors.UnexpectedSmartServerResponse(response_tuple)
837
body = response_handler.read_body_bytes()
839
for line in body.split('\n'):
842
key, val_text = line.split(':')
843
if key in ('revisions', 'size', 'committers'):
844
result[key] = int(val_text)
845
elif key in ('firstrev', 'latestrev'):
846
values = val_text.split(' ')[1:]
847
result[key] = (float(values[0]), long(values[1]))
851
def find_branches(self, using=False):
852
"""See Repository.find_branches()."""
853
# should be an API call to the server.
855
return self._real_repository.find_branches(using=using)
857
def get_physical_lock_status(self):
858
"""See Repository.get_physical_lock_status()."""
859
# should be an API call to the server.
861
return self._real_repository.get_physical_lock_status()
863
def is_in_write_group(self):
864
"""Return True if there is an open write group.
866
write groups are only applicable locally for the smart server..
868
if self._real_repository:
869
return self._real_repository.is_in_write_group()
872
return self._lock_count >= 1
875
"""See Repository.is_shared()."""
876
path = self.bzrdir._path_for_remote_call(self._client)
877
response = self._call('Repository.is_shared', path)
878
if response[0] not in ('yes', 'no'):
879
raise SmartProtocolError('unexpected response code %s' % (response,))
880
return response[0] == 'yes'
882
def is_write_locked(self):
883
return self._lock_mode == 'w'
886
# wrong eventually - want a local lock cache context
887
if not self._lock_mode:
888
self._lock_mode = 'r'
890
self._unstacked_provider.enable_cache(cache_misses=True)
891
if self._real_repository is not None:
892
self._real_repository.lock_read()
893
for repo in self._fallback_repositories:
896
self._lock_count += 1
898
def _remote_lock_write(self, token):
899
path = self.bzrdir._path_for_remote_call(self._client)
902
err_context = {'token': token}
903
response = self._call('Repository.lock_write', path, token,
905
if response[0] == 'ok':
909
raise errors.UnexpectedSmartServerResponse(response)
911
def lock_write(self, token=None, _skip_rpc=False):
912
if not self._lock_mode:
914
if self._lock_token is not None:
915
if token != self._lock_token:
916
raise errors.TokenMismatch(token, self._lock_token)
917
self._lock_token = token
919
self._lock_token = self._remote_lock_write(token)
920
# if self._lock_token is None, then this is something like packs or
921
# svn where we don't get to lock the repo, or a weave style repository
922
# where we cannot lock it over the wire and attempts to do so will
924
if self._real_repository is not None:
925
self._real_repository.lock_write(token=self._lock_token)
926
if token is not None:
927
self._leave_lock = True
929
self._leave_lock = False
930
self._lock_mode = 'w'
932
cache_misses = self._real_repository is None
933
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
934
for repo in self._fallback_repositories:
935
# Writes don't affect fallback repos
937
elif self._lock_mode == 'r':
938
raise errors.ReadOnlyError(self)
940
self._lock_count += 1
941
return self._lock_token or None
943
def leave_lock_in_place(self):
944
if not self._lock_token:
945
raise NotImplementedError(self.leave_lock_in_place)
946
self._leave_lock = True
948
def dont_leave_lock_in_place(self):
949
if not self._lock_token:
950
raise NotImplementedError(self.dont_leave_lock_in_place)
951
self._leave_lock = False
953
def _set_real_repository(self, repository):
954
"""Set the _real_repository for this repository.
956
:param repository: The repository to fallback to for non-hpss
957
implemented operations.
959
if self._real_repository is not None:
960
# Replacing an already set real repository.
961
# We cannot do this [currently] if the repository is locked -
962
# synchronised state might be lost.
964
raise AssertionError('_real_repository is already set')
965
if isinstance(repository, RemoteRepository):
966
raise AssertionError()
967
self._real_repository = repository
968
# three code paths happen here:
969
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
970
# up stacking. In this case self._fallback_repositories is [], and the
971
# real repo is already setup. Preserve the real repo and
972
# RemoteRepository.add_fallback_repository will avoid adding
974
# 2) new servers, RemoteBranch.open() sets up stacking, and when
975
# ensure_real is triggered from a branch, the real repository to
976
# set already has a matching list with separate instances, but
977
# as they are also RemoteRepositories we don't worry about making the
978
# lists be identical.
979
# 3) new servers, RemoteRepository.ensure_real is triggered before
980
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
981
# and need to populate it.
982
if (self._fallback_repositories and
983
len(self._real_repository._fallback_repositories) !=
984
len(self._fallback_repositories)):
985
if len(self._real_repository._fallback_repositories):
986
raise AssertionError(
987
"cannot cleanly remove existing _fallback_repositories")
988
for fb in self._fallback_repositories:
989
self._real_repository.add_fallback_repository(fb)
990
if self._lock_mode == 'w':
991
# if we are already locked, the real repository must be able to
992
# acquire the lock with our token.
993
self._real_repository.lock_write(self._lock_token)
994
elif self._lock_mode == 'r':
995
self._real_repository.lock_read()
997
def start_write_group(self):
998
"""Start a write group on the decorated repository.
1000
Smart methods perform operations in a single step so this API
1001
is not really applicable except as a compatibility thunk
1002
for older plugins that don't use e.g. the CommitBuilder
1006
return self._real_repository.start_write_group()
1008
def _unlock(self, token):
1009
path = self.bzrdir._path_for_remote_call(self._client)
1011
# with no token the remote repository is not persistently locked.
1013
err_context = {'token': token}
1014
response = self._call('Repository.unlock', path, token,
1016
if response == ('ok',):
1019
raise errors.UnexpectedSmartServerResponse(response)
1022
if not self._lock_count:
1023
raise errors.LockNotHeld(self)
1024
self._lock_count -= 1
1025
if self._lock_count > 0:
1027
self._unstacked_provider.disable_cache()
1028
old_mode = self._lock_mode
1029
self._lock_mode = None
1031
# The real repository is responsible at present for raising an
1032
# exception if it's in an unfinished write group. However, it
1033
# normally will *not* actually remove the lock from disk - that's
1034
# done by the server on receiving the Repository.unlock call.
1035
# This is just to let the _real_repository stay up to date.
1036
if self._real_repository is not None:
1037
self._real_repository.unlock()
1039
# The rpc-level lock should be released even if there was a
1040
# problem releasing the vfs-based lock.
1042
# Only write-locked repositories need to make a remote method
1043
# call to perform the unlock.
1044
old_token = self._lock_token
1045
self._lock_token = None
1046
if not self._leave_lock:
1047
self._unlock(old_token)
1048
# Fallbacks are always 'lock_read()' so we don't pay attention to
1050
for repo in self._fallback_repositories:
1053
def break_lock(self):
1054
# should hand off to the network
1056
return self._real_repository.break_lock()
1058
def _get_tarball(self, compression):
1059
"""Return a TemporaryFile containing a repository tarball.
1061
Returns None if the server does not support sending tarballs.
1064
path = self.bzrdir._path_for_remote_call(self._client)
1066
response, protocol = self._call_expecting_body(
1067
'Repository.tarball', path, compression)
1068
except errors.UnknownSmartMethod:
1069
protocol.cancel_read_body()
1071
if response[0] == 'ok':
1072
# Extract the tarball and return it
1073
t = tempfile.NamedTemporaryFile()
1074
# TODO: rpc layer should read directly into it...
1075
t.write(protocol.read_body_bytes())
1078
raise errors.UnexpectedSmartServerResponse(response)
1080
def sprout(self, to_bzrdir, revision_id=None):
1081
# TODO: Option to control what format is created?
1083
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1085
dest_repo.fetch(self, revision_id=revision_id)
1088
### These methods are just thin shims to the VFS object for now.
1090
def revision_tree(self, revision_id):
1092
return self._real_repository.revision_tree(revision_id)
1094
def get_serializer_format(self):
1096
return self._real_repository.get_serializer_format()
1098
def get_commit_builder(self, branch, parents, config, timestamp=None,
1099
timezone=None, committer=None, revprops=None,
1101
# FIXME: It ought to be possible to call this without immediately
1102
# triggering _ensure_real. For now it's the easiest thing to do.
1104
real_repo = self._real_repository
1105
builder = real_repo.get_commit_builder(branch, parents,
1106
config, timestamp=timestamp, timezone=timezone,
1107
committer=committer, revprops=revprops, revision_id=revision_id)
1110
def add_fallback_repository(self, repository):
1111
"""Add a repository to use for looking up data not held locally.
1113
:param repository: A repository.
1115
if not self._format.supports_external_lookups:
1116
raise errors.UnstackableRepositoryFormat(
1117
self._format.network_name(), self.base)
1118
# We need to accumulate additional repositories here, to pass them in
1121
if self.is_locked():
1122
# We will call fallback.unlock() when we transition to the unlocked
1123
# state, so always add a lock here. If a caller passes us a locked
1124
# repository, they are responsible for unlocking it later.
1125
repository.lock_read()
1126
self._fallback_repositories.append(repository)
1127
# If self._real_repository was parameterised already (e.g. because a
1128
# _real_branch had its get_stacked_on_url method called), then the
1129
# repository to be added may already be in the _real_repositories list.
1130
if self._real_repository is not None:
1131
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1132
self._real_repository._fallback_repositories]
1133
if repository.bzrdir.root_transport.base not in fallback_locations:
1134
self._real_repository.add_fallback_repository(repository)
1136
def add_inventory(self, revid, inv, parents):
1138
return self._real_repository.add_inventory(revid, inv, parents)
1140
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1143
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1144
delta, new_revision_id, parents)
1146
def add_revision(self, rev_id, rev, inv=None, config=None):
1148
return self._real_repository.add_revision(
1149
rev_id, rev, inv=inv, config=config)
1152
def get_inventory(self, revision_id):
1154
return self._real_repository.get_inventory(revision_id)
1156
def iter_inventories(self, revision_ids):
1158
return self._real_repository.iter_inventories(revision_ids)
1161
def get_revision(self, revision_id):
1163
return self._real_repository.get_revision(revision_id)
1165
def get_transaction(self):
1167
return self._real_repository.get_transaction()
1170
def clone(self, a_bzrdir, revision_id=None):
1172
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1174
def make_working_trees(self):
1175
"""See Repository.make_working_trees"""
1177
return self._real_repository.make_working_trees()
1179
def refresh_data(self):
1180
"""Re-read any data needed to to synchronise with disk.
1182
This method is intended to be called after another repository instance
1183
(such as one used by a smart server) has inserted data into the
1184
repository. It may not be called during a write group, but may be
1185
called at any other time.
1187
if self.is_in_write_group():
1188
raise errors.InternalBzrError(
1189
"May not refresh_data while in a write group.")
1190
if self._real_repository is not None:
1191
self._real_repository.refresh_data()
1193
def revision_ids_to_search_result(self, result_set):
1194
"""Convert a set of revision ids to a graph SearchResult."""
1195
result_parents = set()
1196
for parents in self.get_graph().get_parent_map(
1197
result_set).itervalues():
1198
result_parents.update(parents)
1199
included_keys = result_set.intersection(result_parents)
1200
start_keys = result_set.difference(included_keys)
1201
exclude_keys = result_parents.difference(result_set)
1202
result = graph.SearchResult(start_keys, exclude_keys,
1203
len(result_set), result_set)
1207
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1208
"""Return the revision ids that other has that this does not.
1210
These are returned in topological order.
1212
revision_id: only return revision ids included by revision_id.
1214
return repository.InterRepository.get(
1215
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1217
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1219
# No base implementation to use as RemoteRepository is not a subclass
1220
# of Repository; so this is a copy of Repository.fetch().
1221
if fetch_spec is not None and revision_id is not None:
1222
raise AssertionError(
1223
"fetch_spec and revision_id are mutually exclusive.")
1224
if self.is_in_write_group():
1225
raise errors.InternalBzrError(
1226
"May not fetch while in a write group.")
1227
# fast path same-url fetch operations
1228
if self.has_same_location(source) and fetch_spec is None:
1229
# check that last_revision is in 'from' and then return a
1231
if (revision_id is not None and
1232
not revision.is_null(revision_id)):
1233
self.get_revision(revision_id)
1235
# if there is no specific appropriate InterRepository, this will get
1236
# the InterRepository base class, which raises an
1237
# IncompatibleRepositories when asked to fetch.
1238
inter = repository.InterRepository.get(source, self)
1239
return inter.fetch(revision_id=revision_id, pb=pb,
1240
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1242
def create_bundle(self, target, base, fileobj, format=None):
1244
self._real_repository.create_bundle(target, base, fileobj, format)
1247
def get_ancestry(self, revision_id, topo_sorted=True):
1249
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1251
def fileids_altered_by_revision_ids(self, revision_ids):
1253
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1255
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1257
return self._real_repository._get_versioned_file_checker(
1258
revisions, revision_versions_cache)
1260
def iter_files_bytes(self, desired_files):
1261
"""See Repository.iter_file_bytes.
1264
return self._real_repository.iter_files_bytes(desired_files)
1266
def get_parent_map(self, revision_ids):
1267
"""See bzrlib.Graph.get_parent_map()."""
1268
return self._make_parents_provider().get_parent_map(revision_ids)
1270
def _get_parent_map_rpc(self, keys):
1271
"""Helper for get_parent_map that performs the RPC."""
1272
medium = self._client._medium
1273
if medium._is_remote_before((1, 2)):
1274
# We already found out that the server can't understand
1275
# Repository.get_parent_map requests, so just fetch the whole
1278
# Note that this reads the whole graph, when only some keys are
1279
# wanted. On this old server there's no way (?) to get them all
1280
# in one go, and the user probably will have seen a warning about
1281
# the server being old anyhow.
1282
rg = self._get_revision_graph(None)
1283
# There is an API discrepancy between get_parent_map and
1284
# get_revision_graph. Specifically, a "key:()" pair in
1285
# get_revision_graph just means a node has no parents. For
1286
# "get_parent_map" it means the node is a ghost. So fix up the
1287
# graph to correct this.
1288
# https://bugs.launchpad.net/bzr/+bug/214894
1289
# There is one other "bug" which is that ghosts in
1290
# get_revision_graph() are not returned at all. But we won't worry
1291
# about that for now.
1292
for node_id, parent_ids in rg.iteritems():
1293
if parent_ids == ():
1294
rg[node_id] = (NULL_REVISION,)
1295
rg[NULL_REVISION] = ()
1300
raise ValueError('get_parent_map(None) is not valid')
1301
if NULL_REVISION in keys:
1302
keys.discard(NULL_REVISION)
1303
found_parents = {NULL_REVISION:()}
1305
return found_parents
1308
# TODO(Needs analysis): We could assume that the keys being requested
1309
# from get_parent_map are in a breadth first search, so typically they
1310
# will all be depth N from some common parent, and we don't have to
1311
# have the server iterate from the root parent, but rather from the
1312
# keys we're searching; and just tell the server the keyspace we
1313
# already have; but this may be more traffic again.
1315
# Transform self._parents_map into a search request recipe.
1316
# TODO: Manage this incrementally to avoid covering the same path
1317
# repeatedly. (The server will have to on each request, but the less
1318
# work done the better).
1320
# Negative caching notes:
1321
# new server sends missing when a request including the revid
1322
# 'include-missing:' is present in the request.
1323
# missing keys are serialised as missing:X, and we then call
1324
# provider.note_missing(X) for-all X
1325
parents_map = self._unstacked_provider.get_cached_map()
1326
if parents_map is None:
1327
# Repository is not locked, so there's no cache.
1329
# start_set is all the keys in the cache
1330
start_set = set(parents_map)
1331
# result set is all the references to keys in the cache
1332
result_parents = set()
1333
for parents in parents_map.itervalues():
1334
result_parents.update(parents)
1335
stop_keys = result_parents.difference(start_set)
1336
# We don't need to send ghosts back to the server as a position to
1338
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1339
key_count = len(parents_map)
1340
if (NULL_REVISION in result_parents
1341
and NULL_REVISION in self._unstacked_provider.missing_keys):
1342
# If we pruned NULL_REVISION from the stop_keys because it's also
1343
# in our cache of "missing" keys we need to increment our key count
1344
# by 1, because the reconsitituted SearchResult on the server will
1345
# still consider NULL_REVISION to be an included key.
1347
included_keys = start_set.intersection(result_parents)
1348
start_set.difference_update(included_keys)
1349
recipe = ('manual', start_set, stop_keys, key_count)
1350
body = self._serialise_search_recipe(recipe)
1351
path = self.bzrdir._path_for_remote_call(self._client)
1353
if type(key) is not str:
1355
"key %r not a plain string" % (key,))
1356
verb = 'Repository.get_parent_map'
1357
args = (path, 'include-missing:') + tuple(keys)
1359
response = self._call_with_body_bytes_expecting_body(
1361
except errors.UnknownSmartMethod:
1362
# Server does not support this method, so get the whole graph.
1363
# Worse, we have to force a disconnection, because the server now
1364
# doesn't realise it has a body on the wire to consume, so the
1365
# only way to recover is to abandon the connection.
1367
'Server is too old for fast get_parent_map, reconnecting. '
1368
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1370
# To avoid having to disconnect repeatedly, we keep track of the
1371
# fact the server doesn't understand remote methods added in 1.2.
1372
medium._remember_remote_is_before((1, 2))
1373
# Recurse just once and we should use the fallback code.
1374
return self._get_parent_map_rpc(keys)
1375
response_tuple, response_handler = response
1376
if response_tuple[0] not in ['ok']:
1377
response_handler.cancel_read_body()
1378
raise errors.UnexpectedSmartServerResponse(response_tuple)
1379
if response_tuple[0] == 'ok':
1380
coded = bz2.decompress(response_handler.read_body_bytes())
1382
# no revisions found
1384
lines = coded.split('\n')
1387
d = tuple(line.split())
1389
revision_graph[d[0]] = d[1:]
1392
if d[0].startswith('missing:'):
1394
self._unstacked_provider.note_missing_key(revid)
1396
# no parents - so give the Graph result
1398
revision_graph[d[0]] = (NULL_REVISION,)
1399
return revision_graph
1402
def get_signature_text(self, revision_id):
1404
return self._real_repository.get_signature_text(revision_id)
1407
def get_inventory_xml(self, revision_id):
1409
return self._real_repository.get_inventory_xml(revision_id)
1411
def deserialise_inventory(self, revision_id, xml):
1413
return self._real_repository.deserialise_inventory(revision_id, xml)
1415
def reconcile(self, other=None, thorough=False):
1417
return self._real_repository.reconcile(other=other, thorough=thorough)
1419
def all_revision_ids(self):
1421
return self._real_repository.all_revision_ids()
1424
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1426
return self._real_repository.get_deltas_for_revisions(revisions,
1427
specific_fileids=specific_fileids)
1430
def get_revision_delta(self, revision_id, specific_fileids=None):
1432
return self._real_repository.get_revision_delta(revision_id,
1433
specific_fileids=specific_fileids)
1436
def revision_trees(self, revision_ids):
1438
return self._real_repository.revision_trees(revision_ids)
1441
def get_revision_reconcile(self, revision_id):
1443
return self._real_repository.get_revision_reconcile(revision_id)
1446
def check(self, revision_ids=None):
1448
return self._real_repository.check(revision_ids=revision_ids)
1450
def copy_content_into(self, destination, revision_id=None):
1452
return self._real_repository.copy_content_into(
1453
destination, revision_id=revision_id)
1455
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1456
# get a tarball of the remote repository, and copy from that into the
1458
from bzrlib import osutils
1460
# TODO: Maybe a progress bar while streaming the tarball?
1461
note("Copying repository content as tarball...")
1462
tar_file = self._get_tarball('bz2')
1463
if tar_file is None:
1465
destination = to_bzrdir.create_repository()
1467
tar = tarfile.open('repository', fileobj=tar_file,
1469
tmpdir = osutils.mkdtemp()
1471
_extract_tar(tar, tmpdir)
1472
tmp_bzrdir = BzrDir.open(tmpdir)
1473
tmp_repo = tmp_bzrdir.open_repository()
1474
tmp_repo.copy_content_into(destination, revision_id)
1476
osutils.rmtree(tmpdir)
1480
# TODO: Suggestion from john: using external tar is much faster than
1481
# python's tarfile library, but it may not work on windows.
1484
def inventories(self):
1485
"""Decorate the real repository for now.
1487
In the long term a full blown network facility is needed to
1488
avoid creating a real repository object locally.
1491
return self._real_repository.inventories
1495
"""Compress the data within the repository.
1497
This is not currently implemented within the smart server.
1500
return self._real_repository.pack()
1503
def revisions(self):
1504
"""Decorate the real repository for now.
1506
In the short term this should become a real object to intercept graph
1509
In the long term a full blown network facility is needed.
1512
return self._real_repository.revisions
1514
def set_make_working_trees(self, new_value):
1516
new_value_str = "True"
1518
new_value_str = "False"
1519
path = self.bzrdir._path_for_remote_call(self._client)
1521
response = self._call(
1522
'Repository.set_make_working_trees', path, new_value_str)
1523
except errors.UnknownSmartMethod:
1525
self._real_repository.set_make_working_trees(new_value)
1527
if response[0] != 'ok':
1528
raise errors.UnexpectedSmartServerResponse(response)
1531
def signatures(self):
1532
"""Decorate the real repository for now.
1534
In the long term a full blown network facility is needed to avoid
1535
creating a real repository object locally.
1538
return self._real_repository.signatures
1541
def sign_revision(self, revision_id, gpg_strategy):
1543
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1547
"""Decorate the real repository for now.
1549
In the long term a full blown network facility is needed to avoid
1550
creating a real repository object locally.
1553
return self._real_repository.texts
1556
def get_revisions(self, revision_ids):
1558
return self._real_repository.get_revisions(revision_ids)
1560
def supports_rich_root(self):
1561
return self._format.rich_root_data
1563
def iter_reverse_revision_history(self, revision_id):
1565
return self._real_repository.iter_reverse_revision_history(revision_id)
1568
def _serializer(self):
1569
return self._format._serializer
1571
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1573
return self._real_repository.store_revision_signature(
1574
gpg_strategy, plaintext, revision_id)
1576
def add_signature_text(self, revision_id, signature):
1578
return self._real_repository.add_signature_text(revision_id, signature)
1580
def has_signature_for_revision_id(self, revision_id):
1582
return self._real_repository.has_signature_for_revision_id(revision_id)
1584
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1586
return self._real_repository.item_keys_introduced_by(revision_ids,
1587
_files_pb=_files_pb)
1589
def revision_graph_can_have_wrong_parents(self):
1590
# The answer depends on the remote repo format.
1592
return self._real_repository.revision_graph_can_have_wrong_parents()
1594
def _find_inconsistent_revision_parents(self):
1596
return self._real_repository._find_inconsistent_revision_parents()
1598
def _check_for_inconsistent_revision_parents(self):
1600
return self._real_repository._check_for_inconsistent_revision_parents()
1602
def _make_parents_provider(self, other=None):
1603
providers = [self._unstacked_provider]
1604
if other is not None:
1605
providers.insert(0, other)
1606
providers.extend(r._make_parents_provider() for r in
1607
self._fallback_repositories)
1608
return graph.StackedParentsProvider(providers)
1610
def _serialise_search_recipe(self, recipe):
1611
"""Serialise a graph search recipe.
1613
:param recipe: A search recipe (start, stop, count).
1614
:return: Serialised bytes.
1616
start_keys = ' '.join(recipe[1])
1617
stop_keys = ' '.join(recipe[2])
1618
count = str(recipe[3])
1619
return '\n'.join((start_keys, stop_keys, count))
1621
def _serialise_search_result(self, search_result):
1622
if isinstance(search_result, graph.PendingAncestryResult):
1623
parts = ['ancestry-of']
1624
parts.extend(search_result.heads)
1626
recipe = search_result.get_recipe()
1627
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1628
return '\n'.join(parts)
1631
path = self.bzrdir._path_for_remote_call(self._client)
1633
response = self._call('PackRepository.autopack', path)
1634
except errors.UnknownSmartMethod:
1636
self._real_repository._pack_collection.autopack()
1639
if response[0] != 'ok':
1640
raise errors.UnexpectedSmartServerResponse(response)
1643
class RemoteStreamSink(repository.StreamSink):
1645
def _insert_real(self, stream, src_format, resume_tokens):
1646
self.target_repo._ensure_real()
1647
sink = self.target_repo._real_repository._get_sink()
1648
result = sink.insert_stream(stream, src_format, resume_tokens)
1650
self.target_repo.autopack()
1653
def insert_stream(self, stream, src_format, resume_tokens):
1654
target = self.target_repo
1655
target._unstacked_provider.missing_keys.clear()
1656
if target._lock_token:
1657
verb = 'Repository.insert_stream_locked'
1658
extra_args = (target._lock_token or '',)
1659
required_version = (1, 14)
1661
verb = 'Repository.insert_stream'
1663
required_version = (1, 13)
1664
client = target._client
1665
medium = client._medium
1666
if medium._is_remote_before(required_version):
1667
# No possible way this can work.
1668
return self._insert_real(stream, src_format, resume_tokens)
1669
path = target.bzrdir._path_for_remote_call(client)
1670
if not resume_tokens:
1671
# XXX: Ugly but important for correctness, *will* be fixed during
1672
# 1.13 cycle. Pushing a stream that is interrupted results in a
1673
# fallback to the _real_repositories sink *with a partial stream*.
1674
# Thats bad because we insert less data than bzr expected. To avoid
1675
# this we do a trial push to make sure the verb is accessible, and
1676
# do not fallback when actually pushing the stream. A cleanup patch
1677
# is going to look at rewinding/restarting the stream/partial
1679
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1681
response = client.call_with_body_stream(
1682
(verb, path, '') + extra_args, byte_stream)
1683
except errors.UnknownSmartMethod:
1684
medium._remember_remote_is_before(required_version)
1685
return self._insert_real(stream, src_format, resume_tokens)
1686
byte_stream = smart_repo._stream_to_byte_stream(
1688
resume_tokens = ' '.join(resume_tokens)
1689
response = client.call_with_body_stream(
1690
(verb, path, resume_tokens) + extra_args, byte_stream)
1691
if response[0][0] not in ('ok', 'missing-basis'):
1692
raise errors.UnexpectedSmartServerResponse(response)
1693
if response[0][0] == 'missing-basis':
1694
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1695
resume_tokens = tokens
1696
return resume_tokens, set(missing_keys)
1698
self.target_repo.refresh_data()
1702
class RemoteStreamSource(repository.StreamSource):
1703
"""Stream data from a remote server."""
1705
def get_stream(self, search):
1706
if (self.from_repository._fallback_repositories and
1707
self.to_format._fetch_order == 'topological'):
1708
return self._real_stream(self.from_repository, search)
1709
return self.missing_parents_chain(search, [self.from_repository] +
1710
self.from_repository._fallback_repositories)
1712
def _real_stream(self, repo, search):
1713
"""Get a stream for search from repo.
1715
This never called RemoteStreamSource.get_stream, and is a heler
1716
for RemoteStreamSource._get_stream to allow getting a stream
1717
reliably whether fallback back because of old servers or trying
1718
to stream from a non-RemoteRepository (which the stacked support
1721
source = repo._get_source(self.to_format)
1722
if isinstance(source, RemoteStreamSource):
1723
return repository.StreamSource.get_stream(source, search)
1724
return source.get_stream(search)
1726
def _get_stream(self, repo, search):
1727
"""Core worker to get a stream from repo for search.
1729
This is used by both get_stream and the stacking support logic. It
1730
deliberately gets a stream for repo which does not need to be
1731
self.from_repository. In the event that repo is not Remote, or
1732
cannot do a smart stream, a fallback is made to the generic
1733
repository._get_stream() interface, via self._real_stream.
1735
In the event of stacking, streams from _get_stream will not
1736
contain all the data for search - this is normal (see get_stream).
1738
:param repo: A repository.
1739
:param search: A search.
1741
# Fallbacks may be non-smart
1742
if not isinstance(repo, RemoteRepository):
1743
return self._real_stream(repo, search)
1744
client = repo._client
1745
medium = client._medium
1746
if medium._is_remote_before((1, 13)):
1747
# streaming was added in 1.13
1748
return self._real_stream(repo, search)
1749
path = repo.bzrdir._path_for_remote_call(client)
1751
search_bytes = repo._serialise_search_result(search)
1752
response = repo._call_with_body_bytes_expecting_body(
1753
'Repository.get_stream',
1754
(path, self.to_format.network_name()), search_bytes)
1755
response_tuple, response_handler = response
1756
except errors.UnknownSmartMethod:
1757
medium._remember_remote_is_before((1,13))
1758
return self._real_stream(repo, search)
1759
if response_tuple[0] != 'ok':
1760
raise errors.UnexpectedSmartServerResponse(response_tuple)
1761
byte_stream = response_handler.read_streamed_body()
1762
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1763
if src_format.network_name() != repo._format.network_name():
1764
raise AssertionError(
1765
"Mismatched RemoteRepository and stream src %r, %r" % (
1766
src_format.network_name(), repo._format.network_name()))
1769
def missing_parents_chain(self, search, sources):
1770
"""Chain multiple streams together to handle stacking.
1772
:param search: The overall search to satisfy with streams.
1773
:param sources: A list of Repository objects to query.
1775
self.serialiser = self.to_format._serializer
1776
self.seen_revs = set()
1777
self.referenced_revs = set()
1778
# If there are heads in the search, or the key count is > 0, we are not
1780
while not search.is_empty() and len(sources) > 1:
1781
source = sources.pop(0)
1782
stream = self._get_stream(source, search)
1783
for kind, substream in stream:
1784
if kind != 'revisions':
1785
yield kind, substream
1787
yield kind, self.missing_parents_rev_handler(substream)
1788
search = search.refine(self.seen_revs, self.referenced_revs)
1789
self.seen_revs = set()
1790
self.referenced_revs = set()
1791
if not search.is_empty():
1792
for kind, stream in self._get_stream(sources[0], search):
1795
def missing_parents_rev_handler(self, substream):
1796
for content in substream:
1797
revision_bytes = content.get_bytes_as('fulltext')
1798
revision = self.serialiser.read_revision_from_string(revision_bytes)
1799
self.seen_revs.add(content.key[-1])
1800
self.referenced_revs.update(revision.parent_ids)
1804
class RemoteBranchLockableFiles(LockableFiles):
1805
"""A 'LockableFiles' implementation that talks to a smart server.
1807
This is not a public interface class.
1810
def __init__(self, bzrdir, _client):
1811
self.bzrdir = bzrdir
1812
self._client = _client
1813
self._need_find_modes = True
1814
LockableFiles.__init__(
1815
self, bzrdir.get_branch_transport(None),
1816
'lock', lockdir.LockDir)
1818
def _find_modes(self):
1819
# RemoteBranches don't let the client set the mode of control files.
1820
self._dir_mode = None
1821
self._file_mode = None
1824
class RemoteBranchFormat(branch.BranchFormat):
1826
def __init__(self, network_name=None):
1827
super(RemoteBranchFormat, self).__init__()
1828
self._matchingbzrdir = RemoteBzrDirFormat()
1829
self._matchingbzrdir.set_branch_format(self)
1830
self._custom_format = None
1831
self._network_name = network_name
1833
def __eq__(self, other):
1834
return (isinstance(other, RemoteBranchFormat) and
1835
self.__dict__ == other.__dict__)
1837
def _ensure_real(self):
1838
if self._custom_format is None:
1839
self._custom_format = branch.network_format_registry.get(
1842
def get_format_description(self):
1843
return 'Remote BZR Branch'
1845
def network_name(self):
1846
return self._network_name
1848
def open(self, a_bzrdir, ignore_fallbacks=False):
1849
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
1851
def _vfs_initialize(self, a_bzrdir):
1852
# Initialisation when using a local bzrdir object, or a non-vfs init
1853
# method is not available on the server.
1854
# self._custom_format is always set - the start of initialize ensures
1856
if isinstance(a_bzrdir, RemoteBzrDir):
1857
a_bzrdir._ensure_real()
1858
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1860
# We assume the bzrdir is parameterised; it may not be.
1861
result = self._custom_format.initialize(a_bzrdir)
1862
if (isinstance(a_bzrdir, RemoteBzrDir) and
1863
not isinstance(result, RemoteBranch)):
1864
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1867
def initialize(self, a_bzrdir):
1868
# 1) get the network name to use.
1869
if self._custom_format:
1870
network_name = self._custom_format.network_name()
1872
# Select the current bzrlib default and ask for that.
1873
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1874
reference_format = reference_bzrdir_format.get_branch_format()
1875
self._custom_format = reference_format
1876
network_name = reference_format.network_name()
1877
# Being asked to create on a non RemoteBzrDir:
1878
if not isinstance(a_bzrdir, RemoteBzrDir):
1879
return self._vfs_initialize(a_bzrdir)
1880
medium = a_bzrdir._client._medium
1881
if medium._is_remote_before((1, 13)):
1882
return self._vfs_initialize(a_bzrdir)
1883
# Creating on a remote bzr dir.
1884
# 2) try direct creation via RPC
1885
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1886
verb = 'BzrDir.create_branch'
1888
response = a_bzrdir._call(verb, path, network_name)
1889
except errors.UnknownSmartMethod:
1890
# Fallback - use vfs methods
1891
medium._remember_remote_is_before((1, 13))
1892
return self._vfs_initialize(a_bzrdir)
1893
if response[0] != 'ok':
1894
raise errors.UnexpectedSmartServerResponse(response)
1895
# Turn the response into a RemoteRepository object.
1896
format = RemoteBranchFormat(network_name=response[1])
1897
repo_format = response_tuple_to_repo_format(response[3:])
1898
if response[2] == '':
1899
repo_bzrdir = a_bzrdir
1901
repo_bzrdir = RemoteBzrDir(
1902
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1904
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1905
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1906
format=format, setup_stacking=False)
1907
# XXX: We know this is a new branch, so it must have revno 0, revid
1908
# NULL_REVISION. Creating the branch locked would make this be unable
1909
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1910
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1911
return remote_branch
1913
def make_tags(self, branch):
1915
return self._custom_format.make_tags(branch)
1917
def supports_tags(self):
1918
# Remote branches might support tags, but we won't know until we
1919
# access the real remote branch.
1921
return self._custom_format.supports_tags()
1923
def supports_stacking(self):
1925
return self._custom_format.supports_stacking()
1927
def supports_set_append_revisions_only(self):
1929
return self._custom_format.supports_set_append_revisions_only()
1932
class RemoteBranch(branch.Branch, _RpcHelper):
1933
"""Branch stored on a server accessed by HPSS RPC.
1935
At the moment most operations are mapped down to simple file operations.
1938
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1939
_client=None, format=None, setup_stacking=True):
1940
"""Create a RemoteBranch instance.
1942
:param real_branch: An optional local implementation of the branch
1943
format, usually accessing the data via the VFS.
1944
:param _client: Private parameter for testing.
1945
:param format: A RemoteBranchFormat object, None to create one
1946
automatically. If supplied it should have a network_name already
1948
:param setup_stacking: If True make an RPC call to determine the
1949
stacked (or not) status of the branch. If False assume the branch
1952
# We intentionally don't call the parent class's __init__, because it
1953
# will try to assign to self.tags, which is a property in this subclass.
1954
# And the parent's __init__ doesn't do much anyway.
1955
self.bzrdir = remote_bzrdir
1956
if _client is not None:
1957
self._client = _client
1959
self._client = remote_bzrdir._client
1960
self.repository = remote_repository
1961
if real_branch is not None:
1962
self._real_branch = real_branch
1963
# Give the remote repository the matching real repo.
1964
real_repo = self._real_branch.repository
1965
if isinstance(real_repo, RemoteRepository):
1966
real_repo._ensure_real()
1967
real_repo = real_repo._real_repository
1968
self.repository._set_real_repository(real_repo)
1969
# Give the branch the remote repository to let fast-pathing happen.
1970
self._real_branch.repository = self.repository
1972
self._real_branch = None
1973
# Fill out expected attributes of branch for bzrlib API users.
1974
self._clear_cached_state()
1975
self.base = self.bzrdir.root_transport.base
1976
self._control_files = None
1977
self._lock_mode = None
1978
self._lock_token = None
1979
self._repo_lock_token = None
1980
self._lock_count = 0
1981
self._leave_lock = False
1982
# Setup a format: note that we cannot call _ensure_real until all the
1983
# attributes above are set: This code cannot be moved higher up in this
1986
self._format = RemoteBranchFormat()
1987
if real_branch is not None:
1988
self._format._network_name = \
1989
self._real_branch._format.network_name()
1991
self._format = format
1992
if not self._format._network_name:
1993
# Did not get from open_branchV2 - old server.
1995
self._format._network_name = \
1996
self._real_branch._format.network_name()
1997
self.tags = self._format.make_tags(self)
1998
# The base class init is not called, so we duplicate this:
1999
hooks = branch.Branch.hooks['open']
2002
self._is_stacked = False
2004
self._setup_stacking()
2006
def _setup_stacking(self):
2007
# configure stacking into the remote repository, by reading it from
2010
fallback_url = self.get_stacked_on_url()
2011
except (errors.NotStacked, errors.UnstackableBranchFormat,
2012
errors.UnstackableRepositoryFormat), e:
2014
self._is_stacked = True
2015
self._activate_fallback_location(fallback_url)
2017
def _get_config(self):
2018
return RemoteBranchConfig(self)
2020
def _get_real_transport(self):
2021
# if we try vfs access, return the real branch's vfs transport
2023
return self._real_branch._transport
2025
_transport = property(_get_real_transport)
2028
return "%s(%s)" % (self.__class__.__name__, self.base)
2032
def _ensure_real(self):
2033
"""Ensure that there is a _real_branch set.
2035
Used before calls to self._real_branch.
2037
if self._real_branch is None:
2038
if not vfs.vfs_enabled():
2039
raise AssertionError('smart server vfs must be enabled '
2040
'to use vfs implementation')
2041
self.bzrdir._ensure_real()
2042
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
2043
if self.repository._real_repository is None:
2044
# Give the remote repository the matching real repo.
2045
real_repo = self._real_branch.repository
2046
if isinstance(real_repo, RemoteRepository):
2047
real_repo._ensure_real()
2048
real_repo = real_repo._real_repository
2049
self.repository._set_real_repository(real_repo)
2050
# Give the real branch the remote repository to let fast-pathing
2052
self._real_branch.repository = self.repository
2053
if self._lock_mode == 'r':
2054
self._real_branch.lock_read()
2055
elif self._lock_mode == 'w':
2056
self._real_branch.lock_write(token=self._lock_token)
2058
def _translate_error(self, err, **context):
2059
self.repository._translate_error(err, branch=self, **context)
2061
def _clear_cached_state(self):
2062
super(RemoteBranch, self)._clear_cached_state()
2063
if self._real_branch is not None:
2064
self._real_branch._clear_cached_state()
2066
def _clear_cached_state_of_remote_branch_only(self):
2067
"""Like _clear_cached_state, but doesn't clear the cache of
2070
This is useful when falling back to calling a method of
2071
self._real_branch that changes state. In that case the underlying
2072
branch changes, so we need to invalidate this RemoteBranch's cache of
2073
it. However, there's no need to invalidate the _real_branch's cache
2074
too, in fact doing so might harm performance.
2076
super(RemoteBranch, self)._clear_cached_state()
2079
def control_files(self):
2080
# Defer actually creating RemoteBranchLockableFiles until its needed,
2081
# because it triggers an _ensure_real that we otherwise might not need.
2082
if self._control_files is None:
2083
self._control_files = RemoteBranchLockableFiles(
2084
self.bzrdir, self._client)
2085
return self._control_files
2087
def _get_checkout_format(self):
2089
return self._real_branch._get_checkout_format()
2091
def get_physical_lock_status(self):
2092
"""See Branch.get_physical_lock_status()."""
2093
# should be an API call to the server, as branches must be lockable.
2095
return self._real_branch.get_physical_lock_status()
2097
def get_stacked_on_url(self):
2098
"""Get the URL this branch is stacked against.
2100
:raises NotStacked: If the branch is not stacked.
2101
:raises UnstackableBranchFormat: If the branch does not support
2103
:raises UnstackableRepositoryFormat: If the repository does not support
2107
# there may not be a repository yet, so we can't use
2108
# self._translate_error, so we can't use self._call either.
2109
response = self._client.call('Branch.get_stacked_on_url',
2110
self._remote_path())
2111
except errors.ErrorFromSmartServer, err:
2112
# there may not be a repository yet, so we can't call through
2113
# its _translate_error
2114
_translate_error(err, branch=self)
2115
except errors.UnknownSmartMethod, err:
2117
return self._real_branch.get_stacked_on_url()
2118
if response[0] != 'ok':
2119
raise errors.UnexpectedSmartServerResponse(response)
2122
def set_stacked_on_url(self, url):
2123
branch.Branch.set_stacked_on_url(self, url)
2125
self._is_stacked = False
2127
self._is_stacked = True
2129
def _vfs_get_tags_bytes(self):
2131
return self._real_branch._get_tags_bytes()
2133
def _get_tags_bytes(self):
2134
medium = self._client._medium
2135
if medium._is_remote_before((1, 13)):
2136
return self._vfs_get_tags_bytes()
2138
response = self._call('Branch.get_tags_bytes', self._remote_path())
2139
except errors.UnknownSmartMethod:
2140
medium._remember_remote_is_before((1, 13))
2141
return self._vfs_get_tags_bytes()
2144
def lock_read(self):
2145
self.repository.lock_read()
2146
if not self._lock_mode:
2147
self._lock_mode = 'r'
2148
self._lock_count = 1
2149
if self._real_branch is not None:
2150
self._real_branch.lock_read()
2152
self._lock_count += 1
2154
def _remote_lock_write(self, token):
2156
branch_token = repo_token = ''
2158
branch_token = token
2159
repo_token = self.repository.lock_write()
2160
self.repository.unlock()
2161
err_context = {'token': token}
2162
response = self._call(
2163
'Branch.lock_write', self._remote_path(), branch_token,
2164
repo_token or '', **err_context)
2165
if response[0] != 'ok':
2166
raise errors.UnexpectedSmartServerResponse(response)
2167
ok, branch_token, repo_token = response
2168
return branch_token, repo_token
2170
def lock_write(self, token=None):
2171
if not self._lock_mode:
2172
# Lock the branch and repo in one remote call.
2173
remote_tokens = self._remote_lock_write(token)
2174
self._lock_token, self._repo_lock_token = remote_tokens
2175
if not self._lock_token:
2176
raise SmartProtocolError('Remote server did not return a token!')
2177
# Tell the self.repository object that it is locked.
2178
self.repository.lock_write(
2179
self._repo_lock_token, _skip_rpc=True)
2181
if self._real_branch is not None:
2182
self._real_branch.lock_write(token=self._lock_token)
2183
if token is not None:
2184
self._leave_lock = True
2186
self._leave_lock = False
2187
self._lock_mode = 'w'
2188
self._lock_count = 1
2189
elif self._lock_mode == 'r':
2190
raise errors.ReadOnlyTransaction
2192
if token is not None:
2193
# A token was given to lock_write, and we're relocking, so
2194
# check that the given token actually matches the one we
2196
if token != self._lock_token:
2197
raise errors.TokenMismatch(token, self._lock_token)
2198
self._lock_count += 1
2199
# Re-lock the repository too.
2200
self.repository.lock_write(self._repo_lock_token)
2201
return self._lock_token or None
2203
def _set_tags_bytes(self, bytes):
2205
return self._real_branch._set_tags_bytes(bytes)
2207
def _unlock(self, branch_token, repo_token):
2208
err_context = {'token': str((branch_token, repo_token))}
2209
response = self._call(
2210
'Branch.unlock', self._remote_path(), branch_token,
2211
repo_token or '', **err_context)
2212
if response == ('ok',):
2214
raise errors.UnexpectedSmartServerResponse(response)
2218
self._lock_count -= 1
2219
if not self._lock_count:
2220
self._clear_cached_state()
2221
mode = self._lock_mode
2222
self._lock_mode = None
2223
if self._real_branch is not None:
2224
if (not self._leave_lock and mode == 'w' and
2225
self._repo_lock_token):
2226
# If this RemoteBranch will remove the physical lock
2227
# for the repository, make sure the _real_branch
2228
# doesn't do it first. (Because the _real_branch's
2229
# repository is set to be the RemoteRepository.)
2230
self._real_branch.repository.leave_lock_in_place()
2231
self._real_branch.unlock()
2233
# Only write-locked branched need to make a remote method
2234
# call to perform the unlock.
2236
if not self._lock_token:
2237
raise AssertionError('Locked, but no token!')
2238
branch_token = self._lock_token
2239
repo_token = self._repo_lock_token
2240
self._lock_token = None
2241
self._repo_lock_token = None
2242
if not self._leave_lock:
2243
self._unlock(branch_token, repo_token)
2245
self.repository.unlock()
2247
def break_lock(self):
2249
return self._real_branch.break_lock()
2251
def leave_lock_in_place(self):
2252
if not self._lock_token:
2253
raise NotImplementedError(self.leave_lock_in_place)
2254
self._leave_lock = True
2256
def dont_leave_lock_in_place(self):
2257
if not self._lock_token:
2258
raise NotImplementedError(self.dont_leave_lock_in_place)
2259
self._leave_lock = False
2261
def get_rev_id(self, revno, history=None):
2263
return _mod_revision.NULL_REVISION
2264
last_revision_info = self.last_revision_info()
2265
ok, result = self.repository.get_rev_id_for_revno(
2266
revno, last_revision_info)
2269
missing_parent = result[1]
2270
# Either the revision named by the server is missing, or its parent
2271
# is. Call get_parent_map to determine which, so that we report a
2273
parent_map = self.repository.get_parent_map([missing_parent])
2274
if missing_parent in parent_map:
2275
missing_parent = parent_map[missing_parent]
2276
raise errors.RevisionNotPresent(missing_parent, self.repository)
2278
def _last_revision_info(self):
2279
response = self._call('Branch.last_revision_info', self._remote_path())
2280
if response[0] != 'ok':
2281
raise SmartProtocolError('unexpected response code %s' % (response,))
2282
revno = int(response[1])
2283
last_revision = response[2]
2284
return (revno, last_revision)
2286
def _gen_revision_history(self):
2287
"""See Branch._gen_revision_history()."""
2288
if self._is_stacked:
2290
return self._real_branch._gen_revision_history()
2291
response_tuple, response_handler = self._call_expecting_body(
2292
'Branch.revision_history', self._remote_path())
2293
if response_tuple[0] != 'ok':
2294
raise errors.UnexpectedSmartServerResponse(response_tuple)
2295
result = response_handler.read_body_bytes().split('\x00')
2300
def _remote_path(self):
2301
return self.bzrdir._path_for_remote_call(self._client)
2303
def _set_last_revision_descendant(self, revision_id, other_branch,
2304
allow_diverged=False, allow_overwrite_descendant=False):
2305
# This performs additional work to meet the hook contract; while its
2306
# undesirable, we have to synthesise the revno to call the hook, and
2307
# not calling the hook is worse as it means changes can't be prevented.
2308
# Having calculated this though, we can't just call into
2309
# set_last_revision_info as a simple call, because there is a set_rh
2310
# hook that some folk may still be using.
2311
old_revno, old_revid = self.last_revision_info()
2312
history = self._lefthand_history(revision_id)
2313
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2314
err_context = {'other_branch': other_branch}
2315
response = self._call('Branch.set_last_revision_ex',
2316
self._remote_path(), self._lock_token, self._repo_lock_token,
2317
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2319
self._clear_cached_state()
2320
if len(response) != 3 and response[0] != 'ok':
2321
raise errors.UnexpectedSmartServerResponse(response)
2322
new_revno, new_revision_id = response[1:]
2323
self._last_revision_info_cache = new_revno, new_revision_id
2324
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2325
if self._real_branch is not None:
2326
cache = new_revno, new_revision_id
2327
self._real_branch._last_revision_info_cache = cache
2329
def _set_last_revision(self, revision_id):
2330
old_revno, old_revid = self.last_revision_info()
2331
# This performs additional work to meet the hook contract; while its
2332
# undesirable, we have to synthesise the revno to call the hook, and
2333
# not calling the hook is worse as it means changes can't be prevented.
2334
# Having calculated this though, we can't just call into
2335
# set_last_revision_info as a simple call, because there is a set_rh
2336
# hook that some folk may still be using.
2337
history = self._lefthand_history(revision_id)
2338
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2339
self._clear_cached_state()
2340
response = self._call('Branch.set_last_revision',
2341
self._remote_path(), self._lock_token, self._repo_lock_token,
2343
if response != ('ok',):
2344
raise errors.UnexpectedSmartServerResponse(response)
2345
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2348
def set_revision_history(self, rev_history):
2349
# Send just the tip revision of the history; the server will generate
2350
# the full history from that. If the revision doesn't exist in this
2351
# branch, NoSuchRevision will be raised.
2352
if rev_history == []:
2355
rev_id = rev_history[-1]
2356
self._set_last_revision(rev_id)
2357
for hook in branch.Branch.hooks['set_rh']:
2358
hook(self, rev_history)
2359
self._cache_revision_history(rev_history)
2361
def _get_parent_location(self):
2362
medium = self._client._medium
2363
if medium._is_remote_before((1, 13)):
2364
return self._vfs_get_parent_location()
2366
response = self._call('Branch.get_parent', self._remote_path())
2367
except errors.UnknownSmartMethod:
2368
medium._remember_remote_is_before((1, 13))
2369
return self._vfs_get_parent_location()
2370
if len(response) != 1:
2371
raise errors.UnexpectedSmartServerResponse(response)
2372
parent_location = response[0]
2373
if parent_location == '':
2375
return parent_location
2377
def _vfs_get_parent_location(self):
2379
return self._real_branch._get_parent_location()
2381
def _set_parent_location(self, url):
2382
medium = self._client._medium
2383
if medium._is_remote_before((1, 15)):
2384
return self._vfs_set_parent_location(url)
2386
call_url = url or ''
2387
if type(call_url) is not str:
2388
raise AssertionError('url must be a str or None (%s)' % url)
2389
response = self._call('Branch.set_parent_location',
2390
self._remote_path(), self._lock_token, self._repo_lock_token,
2392
except errors.UnknownSmartMethod:
2393
medium._remember_remote_is_before((1, 15))
2394
return self._vfs_set_parent_location(url)
2396
raise errors.UnexpectedSmartServerResponse(response)
2398
def _vfs_set_parent_location(self, url):
2400
return self._real_branch._set_parent_location(url)
2403
def pull(self, source, overwrite=False, stop_revision=None,
2405
self._clear_cached_state_of_remote_branch_only()
2407
return self._real_branch.pull(
2408
source, overwrite=overwrite, stop_revision=stop_revision,
2409
_override_hook_target=self, **kwargs)
2412
def push(self, target, overwrite=False, stop_revision=None):
2414
return self._real_branch.push(
2415
target, overwrite=overwrite, stop_revision=stop_revision,
2416
_override_hook_source_branch=self)
2418
def is_locked(self):
2419
return self._lock_count >= 1
2422
def revision_id_to_revno(self, revision_id):
2424
return self._real_branch.revision_id_to_revno(revision_id)
2427
def set_last_revision_info(self, revno, revision_id):
2428
# XXX: These should be returned by the set_last_revision_info verb
2429
old_revno, old_revid = self.last_revision_info()
2430
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2431
revision_id = ensure_null(revision_id)
2433
response = self._call('Branch.set_last_revision_info',
2434
self._remote_path(), self._lock_token, self._repo_lock_token,
2435
str(revno), revision_id)
2436
except errors.UnknownSmartMethod:
2438
self._clear_cached_state_of_remote_branch_only()
2439
self._real_branch.set_last_revision_info(revno, revision_id)
2440
self._last_revision_info_cache = revno, revision_id
2442
if response == ('ok',):
2443
self._clear_cached_state()
2444
self._last_revision_info_cache = revno, revision_id
2445
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2446
# Update the _real_branch's cache too.
2447
if self._real_branch is not None:
2448
cache = self._last_revision_info_cache
2449
self._real_branch._last_revision_info_cache = cache
2451
raise errors.UnexpectedSmartServerResponse(response)
2454
def generate_revision_history(self, revision_id, last_rev=None,
2456
medium = self._client._medium
2457
if not medium._is_remote_before((1, 6)):
2458
# Use a smart method for 1.6 and above servers
2460
self._set_last_revision_descendant(revision_id, other_branch,
2461
allow_diverged=True, allow_overwrite_descendant=True)
2463
except errors.UnknownSmartMethod:
2464
medium._remember_remote_is_before((1, 6))
2465
self._clear_cached_state_of_remote_branch_only()
2466
self.set_revision_history(self._lefthand_history(revision_id,
2467
last_rev=last_rev,other_branch=other_branch))
2469
def set_push_location(self, location):
2471
return self._real_branch.set_push_location(location)
2474
class RemoteConfig(object):
2475
"""A Config that reads and writes from smart verbs.
2477
It is a low-level object that considers config data to be name/value pairs
2478
that may be associated with a section. Assigning meaning to the these
2479
values is done at higher levels like bzrlib.config.TreeConfig.
2482
def get_option(self, name, section=None, default=None):
2483
"""Return the value associated with a named option.
2485
:param name: The name of the value
2486
:param section: The section the option is in (if any)
2487
:param default: The value to return if the value is not set
2488
:return: The value or default value
2491
configobj = self._get_configobj()
2493
section_obj = configobj
2496
section_obj = configobj[section]
2499
return section_obj.get(name, default)
2500
except errors.UnknownSmartMethod:
2501
return self._vfs_get_option(name, section, default)
2503
def _response_to_configobj(self, response):
2504
if len(response[0]) and response[0][0] != 'ok':
2505
raise errors.UnexpectedSmartServerResponse(response)
2506
lines = response[1].read_body_bytes().splitlines()
2507
return config.ConfigObj(lines, encoding='utf-8')
2510
class RemoteBranchConfig(RemoteConfig):
2511
"""A RemoteConfig for Branches."""
2513
def __init__(self, branch):
2514
self._branch = branch
2516
def _get_configobj(self):
2517
path = self._branch._remote_path()
2518
response = self._branch._client.call_expecting_body(
2519
'Branch.get_config_file', path)
2520
return self._response_to_configobj(response)
2522
def set_option(self, value, name, section=None):
2523
"""Set the value associated with a named option.
2525
:param value: The value to set
2526
:param name: The name of the value to set
2527
:param section: The section the option is in (if any)
2529
medium = self._branch._client._medium
2530
if medium._is_remote_before((1, 14)):
2531
return self._vfs_set_option(value, name, section)
2533
path = self._branch._remote_path()
2534
response = self._branch._client.call('Branch.set_config_option',
2535
path, self._branch._lock_token, self._branch._repo_lock_token,
2536
value.encode('utf8'), name, section or '')
2537
except errors.UnknownSmartMethod:
2538
medium._remember_remote_is_before((1, 14))
2539
return self._vfs_set_option(value, name, section)
2541
raise errors.UnexpectedSmartServerResponse(response)
2543
def _real_object(self):
2544
self._branch._ensure_real()
2545
return self._branch._real_branch
2547
def _vfs_set_option(self, value, name, section=None):
2548
return self._real_object()._get_config().set_option(
2549
value, name, section)
2552
class RemoteBzrDirConfig(RemoteConfig):
2553
"""A RemoteConfig for BzrDirs."""
2555
def __init__(self, bzrdir):
2556
self._bzrdir = bzrdir
2558
def _get_configobj(self):
2559
medium = self._bzrdir._client._medium
2560
verb = 'BzrDir.get_config_file'
2561
if medium._is_remote_before((1, 15)):
2562
raise errors.UnknownSmartMethod(verb)
2563
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2564
response = self._bzrdir._call_expecting_body(
2566
return self._response_to_configobj(response)
2568
def _vfs_get_option(self, name, section, default):
2569
return self._real_object()._get_config().get_option(
2570
name, section, default)
2572
def set_option(self, value, name, section=None):
2573
"""Set the value associated with a named option.
2575
:param value: The value to set
2576
:param name: The name of the value to set
2577
:param section: The section the option is in (if any)
2579
return self._real_object()._get_config().set_option(
2580
value, name, section)
2582
def _real_object(self):
2583
self._bzrdir._ensure_real()
2584
return self._bzrdir._real_bzrdir
2588
def _extract_tar(tar, to_dir):
2589
"""Extract all the contents of a tarfile object.
2591
A replacement for extractall, which is not present in python2.4
2594
tar.extract(tarinfo, to_dir)
2597
def _translate_error(err, **context):
2598
"""Translate an ErrorFromSmartServer into a more useful error.
2600
Possible context keys:
2608
If the error from the server doesn't match a known pattern, then
2609
UnknownErrorFromSmartServer is raised.
2613
return context[name]
2614
except KeyError, key_err:
2615
mutter('Missing key %r in context %r', key_err.args[0], context)
2618
"""Get the path from the context if present, otherwise use first error
2622
return context['path']
2623
except KeyError, key_err:
2625
return err.error_args[0]
2626
except IndexError, idx_err:
2628
'Missing key %r in context %r', key_err.args[0], context)
2631
if err.error_verb == 'NoSuchRevision':
2632
raise NoSuchRevision(find('branch'), err.error_args[0])
2633
elif err.error_verb == 'nosuchrevision':
2634
raise NoSuchRevision(find('repository'), err.error_args[0])
2635
elif err.error_tuple == ('nobranch',):
2636
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2637
elif err.error_verb == 'norepository':
2638
raise errors.NoRepositoryPresent(find('bzrdir'))
2639
elif err.error_verb == 'LockContention':
2640
raise errors.LockContention('(remote lock)')
2641
elif err.error_verb == 'UnlockableTransport':
2642
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2643
elif err.error_verb == 'LockFailed':
2644
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2645
elif err.error_verb == 'TokenMismatch':
2646
raise errors.TokenMismatch(find('token'), '(remote token)')
2647
elif err.error_verb == 'Diverged':
2648
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2649
elif err.error_verb == 'TipChangeRejected':
2650
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2651
elif err.error_verb == 'UnstackableBranchFormat':
2652
raise errors.UnstackableBranchFormat(*err.error_args)
2653
elif err.error_verb == 'UnstackableRepositoryFormat':
2654
raise errors.UnstackableRepositoryFormat(*err.error_args)
2655
elif err.error_verb == 'NotStacked':
2656
raise errors.NotStacked(branch=find('branch'))
2657
elif err.error_verb == 'PermissionDenied':
2659
if len(err.error_args) >= 2:
2660
extra = err.error_args[1]
2663
raise errors.PermissionDenied(path, extra=extra)
2664
elif err.error_verb == 'ReadError':
2666
raise errors.ReadError(path)
2667
elif err.error_verb == 'NoSuchFile':
2669
raise errors.NoSuchFile(path)
2670
elif err.error_verb == 'FileExists':
2671
raise errors.FileExists(err.error_args[0])
2672
elif err.error_verb == 'DirectoryNotEmpty':
2673
raise errors.DirectoryNotEmpty(err.error_args[0])
2674
elif err.error_verb == 'ShortReadvError':
2675
args = err.error_args
2676
raise errors.ShortReadvError(
2677
args[0], int(args[1]), int(args[2]), int(args[3]))
2678
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2679
encoding = str(err.error_args[0]) # encoding must always be a string
2680
val = err.error_args[1]
2681
start = int(err.error_args[2])
2682
end = int(err.error_args[3])
2683
reason = str(err.error_args[4]) # reason must always be a string
2684
if val.startswith('u:'):
2685
val = val[2:].decode('utf-8')
2686
elif val.startswith('s:'):
2687
val = val[2:].decode('base64')
2688
if err.error_verb == 'UnicodeDecodeError':
2689
raise UnicodeDecodeError(encoding, val, start, end, reason)
2690
elif err.error_verb == 'UnicodeEncodeError':
2691
raise UnicodeEncodeError(encoding, val, start, end, reason)
2692
elif err.error_verb == 'ReadOnlyError':
2693
raise errors.TransportNotPossible('readonly transport')
2694
raise errors.UnknownErrorFromSmartServer(err)