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 pack_compresses(self):
571
return self._custom_format.pack_compresses
574
def _serializer(self):
576
return self._custom_format._serializer
579
class RemoteRepository(_RpcHelper):
580
"""Repository accessed over rpc.
582
For the moment most operations are performed using local transport-backed
586
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
587
"""Create a RemoteRepository instance.
589
:param remote_bzrdir: The bzrdir hosting this repository.
590
:param format: The RemoteFormat object to use.
591
:param real_repository: If not None, a local implementation of the
592
repository logic for the repository, usually accessing the data
594
:param _client: Private testing parameter - override the smart client
595
to be used by the repository.
598
self._real_repository = real_repository
600
self._real_repository = None
601
self.bzrdir = remote_bzrdir
603
self._client = remote_bzrdir._client
605
self._client = _client
606
self._format = format
607
self._lock_mode = None
608
self._lock_token = None
610
self._leave_lock = False
611
# Cache of revision parents; misses are cached during read locks, and
612
# write locks when no _real_repository has been set.
613
self._unstacked_provider = graph.CachingParentsProvider(
614
get_parent_map=self._get_parent_map_rpc)
615
self._unstacked_provider.disable_cache()
617
# These depend on the actual remote format, so force them off for
618
# maximum compatibility. XXX: In future these should depend on the
619
# remote repository instance, but this is irrelevant until we perform
620
# reconcile via an RPC call.
621
self._reconcile_does_inventory_gc = False
622
self._reconcile_fixes_text_parents = False
623
self._reconcile_backsup_inventory = False
624
self.base = self.bzrdir.transport.base
625
# Additional places to query for data.
626
self._fallback_repositories = []
629
return "%s(%s)" % (self.__class__.__name__, self.base)
633
def abort_write_group(self, suppress_errors=False):
634
"""Complete a write group on the decorated repository.
636
Smart methods perform operations in a single step so this API
637
is not really applicable except as a compatibility thunk
638
for older plugins that don't use e.g. the CommitBuilder
641
:param suppress_errors: see Repository.abort_write_group.
644
return self._real_repository.abort_write_group(
645
suppress_errors=suppress_errors)
649
"""Decorate the real repository for now.
651
In the long term a full blown network facility is needed to avoid
652
creating a real repository object locally.
655
return self._real_repository.chk_bytes
657
def commit_write_group(self):
658
"""Complete a write group on the decorated repository.
660
Smart methods perform operations in a single step so this API
661
is not really applicable except as a compatibility thunk
662
for older plugins that don't use e.g. the CommitBuilder
666
return self._real_repository.commit_write_group()
668
def resume_write_group(self, tokens):
670
return self._real_repository.resume_write_group(tokens)
672
def suspend_write_group(self):
674
return self._real_repository.suspend_write_group()
676
def get_missing_parent_inventories(self, check_for_missing_texts=True):
678
return self._real_repository.get_missing_parent_inventories(
679
check_for_missing_texts=check_for_missing_texts)
681
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
683
return self._real_repository.get_rev_id_for_revno(
686
def get_rev_id_for_revno(self, revno, known_pair):
687
"""See Repository.get_rev_id_for_revno."""
688
path = self.bzrdir._path_for_remote_call(self._client)
690
if self._client._medium._is_remote_before((1, 17)):
691
return self._get_rev_id_for_revno_vfs(revno, known_pair)
692
response = self._call(
693
'Repository.get_rev_id_for_revno', path, revno, known_pair)
694
except errors.UnknownSmartMethod:
695
self._client._medium._remember_remote_is_before((1, 17))
696
return self._get_rev_id_for_revno_vfs(revno, known_pair)
697
if response[0] == 'ok':
698
return True, response[1]
699
elif response[0] == 'history-incomplete':
700
known_pair = response[1:3]
701
for fallback in self._fallback_repositories:
702
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
707
# Not found in any fallbacks
708
return False, known_pair
710
raise errors.UnexpectedSmartServerResponse(response)
712
def _ensure_real(self):
713
"""Ensure that there is a _real_repository set.
715
Used before calls to self._real_repository.
717
Note that _ensure_real causes many roundtrips to the server which are
718
not desirable, and prevents the use of smart one-roundtrip RPC's to
719
perform complex operations (such as accessing parent data, streaming
720
revisions etc). Adding calls to _ensure_real should only be done when
721
bringing up new functionality, adding fallbacks for smart methods that
722
require a fallback path, and never to replace an existing smart method
723
invocation. If in doubt chat to the bzr network team.
725
if self._real_repository is None:
726
if 'hpss' in debug.debug_flags:
728
warning('VFS Repository access triggered\n%s',
729
''.join(traceback.format_stack()))
730
self._unstacked_provider.missing_keys.clear()
731
self.bzrdir._ensure_real()
732
self._set_real_repository(
733
self.bzrdir._real_bzrdir.open_repository())
735
def _translate_error(self, err, **context):
736
self.bzrdir._translate_error(err, repository=self, **context)
738
def find_text_key_references(self):
739
"""Find the text key references within the repository.
741
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
742
revision_ids. Each altered file-ids has the exact revision_ids that
743
altered it listed explicitly.
744
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
745
to whether they were referred to by the inventory of the
746
revision_id that they contain. The inventory texts from all present
747
revision ids are assessed to generate this report.
750
return self._real_repository.find_text_key_references()
752
def _generate_text_key_index(self):
753
"""Generate a new text key index for the repository.
755
This is an expensive function that will take considerable time to run.
757
:return: A dict mapping (file_id, revision_id) tuples to a list of
758
parents, also (file_id, revision_id) tuples.
761
return self._real_repository._generate_text_key_index()
763
def _get_revision_graph(self, revision_id):
764
"""Private method for using with old (< 1.2) servers to fallback."""
765
if revision_id is None:
767
elif revision.is_null(revision_id):
770
path = self.bzrdir._path_for_remote_call(self._client)
771
response = self._call_expecting_body(
772
'Repository.get_revision_graph', path, revision_id)
773
response_tuple, response_handler = response
774
if response_tuple[0] != 'ok':
775
raise errors.UnexpectedSmartServerResponse(response_tuple)
776
coded = response_handler.read_body_bytes()
778
# no revisions in this repository!
780
lines = coded.split('\n')
783
d = tuple(line.split())
784
revision_graph[d[0]] = d[1:]
786
return revision_graph
789
"""See Repository._get_sink()."""
790
return RemoteStreamSink(self)
792
def _get_source(self, to_format):
793
"""Return a source for streaming from this repository."""
794
return RemoteStreamSource(self, to_format)
797
def has_revision(self, revision_id):
798
"""True if this repository has a copy of the revision."""
799
# Copy of bzrlib.repository.Repository.has_revision
800
return revision_id in self.has_revisions((revision_id,))
803
def has_revisions(self, revision_ids):
804
"""Probe to find out the presence of multiple revisions.
806
:param revision_ids: An iterable of revision_ids.
807
:return: A set of the revision_ids that were present.
809
# Copy of bzrlib.repository.Repository.has_revisions
810
parent_map = self.get_parent_map(revision_ids)
811
result = set(parent_map)
812
if _mod_revision.NULL_REVISION in revision_ids:
813
result.add(_mod_revision.NULL_REVISION)
816
def has_same_location(self, other):
817
return (self.__class__ is other.__class__ and
818
self.bzrdir.transport.base == other.bzrdir.transport.base)
820
def get_graph(self, other_repository=None):
821
"""Return the graph for this repository format"""
822
parents_provider = self._make_parents_provider(other_repository)
823
return graph.Graph(parents_provider)
825
def gather_stats(self, revid=None, committers=None):
826
"""See Repository.gather_stats()."""
827
path = self.bzrdir._path_for_remote_call(self._client)
828
# revid can be None to indicate no revisions, not just NULL_REVISION
829
if revid is None or revision.is_null(revid):
833
if committers is None or not committers:
834
fmt_committers = 'no'
836
fmt_committers = 'yes'
837
response_tuple, response_handler = self._call_expecting_body(
838
'Repository.gather_stats', path, fmt_revid, fmt_committers)
839
if response_tuple[0] != 'ok':
840
raise errors.UnexpectedSmartServerResponse(response_tuple)
842
body = response_handler.read_body_bytes()
844
for line in body.split('\n'):
847
key, val_text = line.split(':')
848
if key in ('revisions', 'size', 'committers'):
849
result[key] = int(val_text)
850
elif key in ('firstrev', 'latestrev'):
851
values = val_text.split(' ')[1:]
852
result[key] = (float(values[0]), long(values[1]))
856
def find_branches(self, using=False):
857
"""See Repository.find_branches()."""
858
# should be an API call to the server.
860
return self._real_repository.find_branches(using=using)
862
def get_physical_lock_status(self):
863
"""See Repository.get_physical_lock_status()."""
864
# should be an API call to the server.
866
return self._real_repository.get_physical_lock_status()
868
def is_in_write_group(self):
869
"""Return True if there is an open write group.
871
write groups are only applicable locally for the smart server..
873
if self._real_repository:
874
return self._real_repository.is_in_write_group()
877
return self._lock_count >= 1
880
"""See Repository.is_shared()."""
881
path = self.bzrdir._path_for_remote_call(self._client)
882
response = self._call('Repository.is_shared', path)
883
if response[0] not in ('yes', 'no'):
884
raise SmartProtocolError('unexpected response code %s' % (response,))
885
return response[0] == 'yes'
887
def is_write_locked(self):
888
return self._lock_mode == 'w'
891
# wrong eventually - want a local lock cache context
892
if not self._lock_mode:
893
self._lock_mode = 'r'
895
self._unstacked_provider.enable_cache(cache_misses=True)
896
if self._real_repository is not None:
897
self._real_repository.lock_read()
898
for repo in self._fallback_repositories:
901
self._lock_count += 1
903
def _remote_lock_write(self, token):
904
path = self.bzrdir._path_for_remote_call(self._client)
907
err_context = {'token': token}
908
response = self._call('Repository.lock_write', path, token,
910
if response[0] == 'ok':
914
raise errors.UnexpectedSmartServerResponse(response)
916
def lock_write(self, token=None, _skip_rpc=False):
917
if not self._lock_mode:
919
if self._lock_token is not None:
920
if token != self._lock_token:
921
raise errors.TokenMismatch(token, self._lock_token)
922
self._lock_token = token
924
self._lock_token = self._remote_lock_write(token)
925
# if self._lock_token is None, then this is something like packs or
926
# svn where we don't get to lock the repo, or a weave style repository
927
# where we cannot lock it over the wire and attempts to do so will
929
if self._real_repository is not None:
930
self._real_repository.lock_write(token=self._lock_token)
931
if token is not None:
932
self._leave_lock = True
934
self._leave_lock = False
935
self._lock_mode = 'w'
937
cache_misses = self._real_repository is None
938
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
939
for repo in self._fallback_repositories:
940
# Writes don't affect fallback repos
942
elif self._lock_mode == 'r':
943
raise errors.ReadOnlyError(self)
945
self._lock_count += 1
946
return self._lock_token or None
948
def leave_lock_in_place(self):
949
if not self._lock_token:
950
raise NotImplementedError(self.leave_lock_in_place)
951
self._leave_lock = True
953
def dont_leave_lock_in_place(self):
954
if not self._lock_token:
955
raise NotImplementedError(self.dont_leave_lock_in_place)
956
self._leave_lock = False
958
def _set_real_repository(self, repository):
959
"""Set the _real_repository for this repository.
961
:param repository: The repository to fallback to for non-hpss
962
implemented operations.
964
if self._real_repository is not None:
965
# Replacing an already set real repository.
966
# We cannot do this [currently] if the repository is locked -
967
# synchronised state might be lost.
969
raise AssertionError('_real_repository is already set')
970
if isinstance(repository, RemoteRepository):
971
raise AssertionError()
972
self._real_repository = repository
973
# three code paths happen here:
974
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
975
# up stacking. In this case self._fallback_repositories is [], and the
976
# real repo is already setup. Preserve the real repo and
977
# RemoteRepository.add_fallback_repository will avoid adding
979
# 2) new servers, RemoteBranch.open() sets up stacking, and when
980
# ensure_real is triggered from a branch, the real repository to
981
# set already has a matching list with separate instances, but
982
# as they are also RemoteRepositories we don't worry about making the
983
# lists be identical.
984
# 3) new servers, RemoteRepository.ensure_real is triggered before
985
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
986
# and need to populate it.
987
if (self._fallback_repositories and
988
len(self._real_repository._fallback_repositories) !=
989
len(self._fallback_repositories)):
990
if len(self._real_repository._fallback_repositories):
991
raise AssertionError(
992
"cannot cleanly remove existing _fallback_repositories")
993
for fb in self._fallback_repositories:
994
self._real_repository.add_fallback_repository(fb)
995
if self._lock_mode == 'w':
996
# if we are already locked, the real repository must be able to
997
# acquire the lock with our token.
998
self._real_repository.lock_write(self._lock_token)
999
elif self._lock_mode == 'r':
1000
self._real_repository.lock_read()
1002
def start_write_group(self):
1003
"""Start a write group on the decorated repository.
1005
Smart methods perform operations in a single step so this API
1006
is not really applicable except as a compatibility thunk
1007
for older plugins that don't use e.g. the CommitBuilder
1011
return self._real_repository.start_write_group()
1013
def _unlock(self, token):
1014
path = self.bzrdir._path_for_remote_call(self._client)
1016
# with no token the remote repository is not persistently locked.
1018
err_context = {'token': token}
1019
response = self._call('Repository.unlock', path, token,
1021
if response == ('ok',):
1024
raise errors.UnexpectedSmartServerResponse(response)
1027
if not self._lock_count:
1028
raise errors.LockNotHeld(self)
1029
self._lock_count -= 1
1030
if self._lock_count > 0:
1032
self._unstacked_provider.disable_cache()
1033
old_mode = self._lock_mode
1034
self._lock_mode = None
1036
# The real repository is responsible at present for raising an
1037
# exception if it's in an unfinished write group. However, it
1038
# normally will *not* actually remove the lock from disk - that's
1039
# done by the server on receiving the Repository.unlock call.
1040
# This is just to let the _real_repository stay up to date.
1041
if self._real_repository is not None:
1042
self._real_repository.unlock()
1044
# The rpc-level lock should be released even if there was a
1045
# problem releasing the vfs-based lock.
1047
# Only write-locked repositories need to make a remote method
1048
# call to perform the unlock.
1049
old_token = self._lock_token
1050
self._lock_token = None
1051
if not self._leave_lock:
1052
self._unlock(old_token)
1053
# Fallbacks are always 'lock_read()' so we don't pay attention to
1055
for repo in self._fallback_repositories:
1058
def break_lock(self):
1059
# should hand off to the network
1061
return self._real_repository.break_lock()
1063
def _get_tarball(self, compression):
1064
"""Return a TemporaryFile containing a repository tarball.
1066
Returns None if the server does not support sending tarballs.
1069
path = self.bzrdir._path_for_remote_call(self._client)
1071
response, protocol = self._call_expecting_body(
1072
'Repository.tarball', path, compression)
1073
except errors.UnknownSmartMethod:
1074
protocol.cancel_read_body()
1076
if response[0] == 'ok':
1077
# Extract the tarball and return it
1078
t = tempfile.NamedTemporaryFile()
1079
# TODO: rpc layer should read directly into it...
1080
t.write(protocol.read_body_bytes())
1083
raise errors.UnexpectedSmartServerResponse(response)
1085
def sprout(self, to_bzrdir, revision_id=None):
1086
# TODO: Option to control what format is created?
1088
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1090
dest_repo.fetch(self, revision_id=revision_id)
1093
### These methods are just thin shims to the VFS object for now.
1095
def revision_tree(self, revision_id):
1097
return self._real_repository.revision_tree(revision_id)
1099
def get_serializer_format(self):
1101
return self._real_repository.get_serializer_format()
1103
def get_commit_builder(self, branch, parents, config, timestamp=None,
1104
timezone=None, committer=None, revprops=None,
1106
# FIXME: It ought to be possible to call this without immediately
1107
# triggering _ensure_real. For now it's the easiest thing to do.
1109
real_repo = self._real_repository
1110
builder = real_repo.get_commit_builder(branch, parents,
1111
config, timestamp=timestamp, timezone=timezone,
1112
committer=committer, revprops=revprops, revision_id=revision_id)
1115
def add_fallback_repository(self, repository):
1116
"""Add a repository to use for looking up data not held locally.
1118
:param repository: A repository.
1120
if not self._format.supports_external_lookups:
1121
raise errors.UnstackableRepositoryFormat(
1122
self._format.network_name(), self.base)
1123
# We need to accumulate additional repositories here, to pass them in
1126
if self.is_locked():
1127
# We will call fallback.unlock() when we transition to the unlocked
1128
# state, so always add a lock here. If a caller passes us a locked
1129
# repository, they are responsible for unlocking it later.
1130
repository.lock_read()
1131
self._fallback_repositories.append(repository)
1132
# If self._real_repository was parameterised already (e.g. because a
1133
# _real_branch had its get_stacked_on_url method called), then the
1134
# repository to be added may already be in the _real_repositories list.
1135
if self._real_repository is not None:
1136
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1137
self._real_repository._fallback_repositories]
1138
if repository.bzrdir.root_transport.base not in fallback_locations:
1139
self._real_repository.add_fallback_repository(repository)
1141
def add_inventory(self, revid, inv, parents):
1143
return self._real_repository.add_inventory(revid, inv, parents)
1145
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1148
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1149
delta, new_revision_id, parents)
1151
def add_revision(self, rev_id, rev, inv=None, config=None):
1153
return self._real_repository.add_revision(
1154
rev_id, rev, inv=inv, config=config)
1157
def get_inventory(self, revision_id):
1159
return self._real_repository.get_inventory(revision_id)
1161
def iter_inventories(self, revision_ids):
1163
return self._real_repository.iter_inventories(revision_ids)
1166
def get_revision(self, revision_id):
1168
return self._real_repository.get_revision(revision_id)
1170
def get_transaction(self):
1172
return self._real_repository.get_transaction()
1175
def clone(self, a_bzrdir, revision_id=None):
1177
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1179
def make_working_trees(self):
1180
"""See Repository.make_working_trees"""
1182
return self._real_repository.make_working_trees()
1184
def refresh_data(self):
1185
"""Re-read any data needed to to synchronise with disk.
1187
This method is intended to be called after another repository instance
1188
(such as one used by a smart server) has inserted data into the
1189
repository. It may not be called during a write group, but may be
1190
called at any other time.
1192
if self.is_in_write_group():
1193
raise errors.InternalBzrError(
1194
"May not refresh_data while in a write group.")
1195
if self._real_repository is not None:
1196
self._real_repository.refresh_data()
1198
def revision_ids_to_search_result(self, result_set):
1199
"""Convert a set of revision ids to a graph SearchResult."""
1200
result_parents = set()
1201
for parents in self.get_graph().get_parent_map(
1202
result_set).itervalues():
1203
result_parents.update(parents)
1204
included_keys = result_set.intersection(result_parents)
1205
start_keys = result_set.difference(included_keys)
1206
exclude_keys = result_parents.difference(result_set)
1207
result = graph.SearchResult(start_keys, exclude_keys,
1208
len(result_set), result_set)
1212
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1213
"""Return the revision ids that other has that this does not.
1215
These are returned in topological order.
1217
revision_id: only return revision ids included by revision_id.
1219
return repository.InterRepository.get(
1220
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1222
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1224
# No base implementation to use as RemoteRepository is not a subclass
1225
# of Repository; so this is a copy of Repository.fetch().
1226
if fetch_spec is not None and revision_id is not None:
1227
raise AssertionError(
1228
"fetch_spec and revision_id are mutually exclusive.")
1229
if self.is_in_write_group():
1230
raise errors.InternalBzrError(
1231
"May not fetch while in a write group.")
1232
# fast path same-url fetch operations
1233
if self.has_same_location(source) and fetch_spec is None:
1234
# check that last_revision is in 'from' and then return a
1236
if (revision_id is not None and
1237
not revision.is_null(revision_id)):
1238
self.get_revision(revision_id)
1240
# if there is no specific appropriate InterRepository, this will get
1241
# the InterRepository base class, which raises an
1242
# IncompatibleRepositories when asked to fetch.
1243
inter = repository.InterRepository.get(source, self)
1244
return inter.fetch(revision_id=revision_id, pb=pb,
1245
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1247
def create_bundle(self, target, base, fileobj, format=None):
1249
self._real_repository.create_bundle(target, base, fileobj, format)
1252
def get_ancestry(self, revision_id, topo_sorted=True):
1254
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1256
def fileids_altered_by_revision_ids(self, revision_ids):
1258
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1260
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1262
return self._real_repository._get_versioned_file_checker(
1263
revisions, revision_versions_cache)
1265
def iter_files_bytes(self, desired_files):
1266
"""See Repository.iter_file_bytes.
1269
return self._real_repository.iter_files_bytes(desired_files)
1271
def get_parent_map(self, revision_ids):
1272
"""See bzrlib.Graph.get_parent_map()."""
1273
return self._make_parents_provider().get_parent_map(revision_ids)
1275
def _get_parent_map_rpc(self, keys):
1276
"""Helper for get_parent_map that performs the RPC."""
1277
medium = self._client._medium
1278
if medium._is_remote_before((1, 2)):
1279
# We already found out that the server can't understand
1280
# Repository.get_parent_map requests, so just fetch the whole
1283
# Note that this reads the whole graph, when only some keys are
1284
# wanted. On this old server there's no way (?) to get them all
1285
# in one go, and the user probably will have seen a warning about
1286
# the server being old anyhow.
1287
rg = self._get_revision_graph(None)
1288
# There is an API discrepancy between get_parent_map and
1289
# get_revision_graph. Specifically, a "key:()" pair in
1290
# get_revision_graph just means a node has no parents. For
1291
# "get_parent_map" it means the node is a ghost. So fix up the
1292
# graph to correct this.
1293
# https://bugs.launchpad.net/bzr/+bug/214894
1294
# There is one other "bug" which is that ghosts in
1295
# get_revision_graph() are not returned at all. But we won't worry
1296
# about that for now.
1297
for node_id, parent_ids in rg.iteritems():
1298
if parent_ids == ():
1299
rg[node_id] = (NULL_REVISION,)
1300
rg[NULL_REVISION] = ()
1305
raise ValueError('get_parent_map(None) is not valid')
1306
if NULL_REVISION in keys:
1307
keys.discard(NULL_REVISION)
1308
found_parents = {NULL_REVISION:()}
1310
return found_parents
1313
# TODO(Needs analysis): We could assume that the keys being requested
1314
# from get_parent_map are in a breadth first search, so typically they
1315
# will all be depth N from some common parent, and we don't have to
1316
# have the server iterate from the root parent, but rather from the
1317
# keys we're searching; and just tell the server the keyspace we
1318
# already have; but this may be more traffic again.
1320
# Transform self._parents_map into a search request recipe.
1321
# TODO: Manage this incrementally to avoid covering the same path
1322
# repeatedly. (The server will have to on each request, but the less
1323
# work done the better).
1325
# Negative caching notes:
1326
# new server sends missing when a request including the revid
1327
# 'include-missing:' is present in the request.
1328
# missing keys are serialised as missing:X, and we then call
1329
# provider.note_missing(X) for-all X
1330
parents_map = self._unstacked_provider.get_cached_map()
1331
if parents_map is None:
1332
# Repository is not locked, so there's no cache.
1334
# start_set is all the keys in the cache
1335
start_set = set(parents_map)
1336
# result set is all the references to keys in the cache
1337
result_parents = set()
1338
for parents in parents_map.itervalues():
1339
result_parents.update(parents)
1340
stop_keys = result_parents.difference(start_set)
1341
# We don't need to send ghosts back to the server as a position to
1343
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1344
key_count = len(parents_map)
1345
if (NULL_REVISION in result_parents
1346
and NULL_REVISION in self._unstacked_provider.missing_keys):
1347
# If we pruned NULL_REVISION from the stop_keys because it's also
1348
# in our cache of "missing" keys we need to increment our key count
1349
# by 1, because the reconsitituted SearchResult on the server will
1350
# still consider NULL_REVISION to be an included key.
1352
included_keys = start_set.intersection(result_parents)
1353
start_set.difference_update(included_keys)
1354
recipe = ('manual', start_set, stop_keys, key_count)
1355
body = self._serialise_search_recipe(recipe)
1356
path = self.bzrdir._path_for_remote_call(self._client)
1358
if type(key) is not str:
1360
"key %r not a plain string" % (key,))
1361
verb = 'Repository.get_parent_map'
1362
args = (path, 'include-missing:') + tuple(keys)
1364
response = self._call_with_body_bytes_expecting_body(
1366
except errors.UnknownSmartMethod:
1367
# Server does not support this method, so get the whole graph.
1368
# Worse, we have to force a disconnection, because the server now
1369
# doesn't realise it has a body on the wire to consume, so the
1370
# only way to recover is to abandon the connection.
1372
'Server is too old for fast get_parent_map, reconnecting. '
1373
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1375
# To avoid having to disconnect repeatedly, we keep track of the
1376
# fact the server doesn't understand remote methods added in 1.2.
1377
medium._remember_remote_is_before((1, 2))
1378
# Recurse just once and we should use the fallback code.
1379
return self._get_parent_map_rpc(keys)
1380
response_tuple, response_handler = response
1381
if response_tuple[0] not in ['ok']:
1382
response_handler.cancel_read_body()
1383
raise errors.UnexpectedSmartServerResponse(response_tuple)
1384
if response_tuple[0] == 'ok':
1385
coded = bz2.decompress(response_handler.read_body_bytes())
1387
# no revisions found
1389
lines = coded.split('\n')
1392
d = tuple(line.split())
1394
revision_graph[d[0]] = d[1:]
1397
if d[0].startswith('missing:'):
1399
self._unstacked_provider.note_missing_key(revid)
1401
# no parents - so give the Graph result
1403
revision_graph[d[0]] = (NULL_REVISION,)
1404
return revision_graph
1407
def get_signature_text(self, revision_id):
1409
return self._real_repository.get_signature_text(revision_id)
1412
def get_inventory_xml(self, revision_id):
1414
return self._real_repository.get_inventory_xml(revision_id)
1416
def deserialise_inventory(self, revision_id, xml):
1418
return self._real_repository.deserialise_inventory(revision_id, xml)
1420
def reconcile(self, other=None, thorough=False):
1422
return self._real_repository.reconcile(other=other, thorough=thorough)
1424
def all_revision_ids(self):
1426
return self._real_repository.all_revision_ids()
1429
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1431
return self._real_repository.get_deltas_for_revisions(revisions,
1432
specific_fileids=specific_fileids)
1435
def get_revision_delta(self, revision_id, specific_fileids=None):
1437
return self._real_repository.get_revision_delta(revision_id,
1438
specific_fileids=specific_fileids)
1441
def revision_trees(self, revision_ids):
1443
return self._real_repository.revision_trees(revision_ids)
1446
def get_revision_reconcile(self, revision_id):
1448
return self._real_repository.get_revision_reconcile(revision_id)
1451
def check(self, revision_ids=None):
1453
return self._real_repository.check(revision_ids=revision_ids)
1455
def copy_content_into(self, destination, revision_id=None):
1457
return self._real_repository.copy_content_into(
1458
destination, revision_id=revision_id)
1460
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1461
# get a tarball of the remote repository, and copy from that into the
1463
from bzrlib import osutils
1465
# TODO: Maybe a progress bar while streaming the tarball?
1466
note("Copying repository content as tarball...")
1467
tar_file = self._get_tarball('bz2')
1468
if tar_file is None:
1470
destination = to_bzrdir.create_repository()
1472
tar = tarfile.open('repository', fileobj=tar_file,
1474
tmpdir = osutils.mkdtemp()
1476
_extract_tar(tar, tmpdir)
1477
tmp_bzrdir = BzrDir.open(tmpdir)
1478
tmp_repo = tmp_bzrdir.open_repository()
1479
tmp_repo.copy_content_into(destination, revision_id)
1481
osutils.rmtree(tmpdir)
1485
# TODO: Suggestion from john: using external tar is much faster than
1486
# python's tarfile library, but it may not work on windows.
1489
def inventories(self):
1490
"""Decorate the real repository for now.
1492
In the long term a full blown network facility is needed to
1493
avoid creating a real repository object locally.
1496
return self._real_repository.inventories
1499
def pack(self, hint=None):
1500
"""Compress the data within the repository.
1502
This is not currently implemented within the smart server.
1505
return self._real_repository.pack(hint=hint)
1508
def revisions(self):
1509
"""Decorate the real repository for now.
1511
In the short term this should become a real object to intercept graph
1514
In the long term a full blown network facility is needed.
1517
return self._real_repository.revisions
1519
def set_make_working_trees(self, new_value):
1521
new_value_str = "True"
1523
new_value_str = "False"
1524
path = self.bzrdir._path_for_remote_call(self._client)
1526
response = self._call(
1527
'Repository.set_make_working_trees', path, new_value_str)
1528
except errors.UnknownSmartMethod:
1530
self._real_repository.set_make_working_trees(new_value)
1532
if response[0] != 'ok':
1533
raise errors.UnexpectedSmartServerResponse(response)
1536
def signatures(self):
1537
"""Decorate the real repository for now.
1539
In the long term a full blown network facility is needed to avoid
1540
creating a real repository object locally.
1543
return self._real_repository.signatures
1546
def sign_revision(self, revision_id, gpg_strategy):
1548
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1552
"""Decorate the real repository for now.
1554
In the long term a full blown network facility is needed to avoid
1555
creating a real repository object locally.
1558
return self._real_repository.texts
1561
def get_revisions(self, revision_ids):
1563
return self._real_repository.get_revisions(revision_ids)
1565
def supports_rich_root(self):
1566
return self._format.rich_root_data
1568
def iter_reverse_revision_history(self, revision_id):
1570
return self._real_repository.iter_reverse_revision_history(revision_id)
1573
def _serializer(self):
1574
return self._format._serializer
1576
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1578
return self._real_repository.store_revision_signature(
1579
gpg_strategy, plaintext, revision_id)
1581
def add_signature_text(self, revision_id, signature):
1583
return self._real_repository.add_signature_text(revision_id, signature)
1585
def has_signature_for_revision_id(self, revision_id):
1587
return self._real_repository.has_signature_for_revision_id(revision_id)
1589
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1591
return self._real_repository.item_keys_introduced_by(revision_ids,
1592
_files_pb=_files_pb)
1594
def revision_graph_can_have_wrong_parents(self):
1595
# The answer depends on the remote repo format.
1597
return self._real_repository.revision_graph_can_have_wrong_parents()
1599
def _find_inconsistent_revision_parents(self):
1601
return self._real_repository._find_inconsistent_revision_parents()
1603
def _check_for_inconsistent_revision_parents(self):
1605
return self._real_repository._check_for_inconsistent_revision_parents()
1607
def _make_parents_provider(self, other=None):
1608
providers = [self._unstacked_provider]
1609
if other is not None:
1610
providers.insert(0, other)
1611
providers.extend(r._make_parents_provider() for r in
1612
self._fallback_repositories)
1613
return graph.StackedParentsProvider(providers)
1615
def _serialise_search_recipe(self, recipe):
1616
"""Serialise a graph search recipe.
1618
:param recipe: A search recipe (start, stop, count).
1619
:return: Serialised bytes.
1621
start_keys = ' '.join(recipe[1])
1622
stop_keys = ' '.join(recipe[2])
1623
count = str(recipe[3])
1624
return '\n'.join((start_keys, stop_keys, count))
1626
def _serialise_search_result(self, search_result):
1627
if isinstance(search_result, graph.PendingAncestryResult):
1628
parts = ['ancestry-of']
1629
parts.extend(search_result.heads)
1631
recipe = search_result.get_recipe()
1632
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1633
return '\n'.join(parts)
1636
path = self.bzrdir._path_for_remote_call(self._client)
1638
response = self._call('PackRepository.autopack', path)
1639
except errors.UnknownSmartMethod:
1641
self._real_repository._pack_collection.autopack()
1644
if response[0] != 'ok':
1645
raise errors.UnexpectedSmartServerResponse(response)
1648
class RemoteStreamSink(repository.StreamSink):
1650
def _insert_real(self, stream, src_format, resume_tokens):
1651
self.target_repo._ensure_real()
1652
sink = self.target_repo._real_repository._get_sink()
1653
result = sink.insert_stream(stream, src_format, resume_tokens)
1655
self.target_repo.autopack()
1658
def insert_stream(self, stream, src_format, resume_tokens):
1659
target = self.target_repo
1660
target._unstacked_provider.missing_keys.clear()
1661
if target._lock_token:
1662
verb = 'Repository.insert_stream_locked'
1663
extra_args = (target._lock_token or '',)
1664
required_version = (1, 14)
1666
verb = 'Repository.insert_stream'
1668
required_version = (1, 13)
1669
client = target._client
1670
medium = client._medium
1671
if medium._is_remote_before(required_version):
1672
# No possible way this can work.
1673
return self._insert_real(stream, src_format, resume_tokens)
1674
path = target.bzrdir._path_for_remote_call(client)
1675
if not resume_tokens:
1676
# XXX: Ugly but important for correctness, *will* be fixed during
1677
# 1.13 cycle. Pushing a stream that is interrupted results in a
1678
# fallback to the _real_repositories sink *with a partial stream*.
1679
# Thats bad because we insert less data than bzr expected. To avoid
1680
# this we do a trial push to make sure the verb is accessible, and
1681
# do not fallback when actually pushing the stream. A cleanup patch
1682
# is going to look at rewinding/restarting the stream/partial
1684
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1686
response = client.call_with_body_stream(
1687
(verb, path, '') + extra_args, byte_stream)
1688
except errors.UnknownSmartMethod:
1689
medium._remember_remote_is_before(required_version)
1690
return self._insert_real(stream, src_format, resume_tokens)
1691
byte_stream = smart_repo._stream_to_byte_stream(
1693
resume_tokens = ' '.join(resume_tokens)
1694
response = client.call_with_body_stream(
1695
(verb, path, resume_tokens) + extra_args, byte_stream)
1696
if response[0][0] not in ('ok', 'missing-basis'):
1697
raise errors.UnexpectedSmartServerResponse(response)
1698
if response[0][0] == 'missing-basis':
1699
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1700
resume_tokens = tokens
1701
return resume_tokens, set(missing_keys)
1703
self.target_repo.refresh_data()
1707
class RemoteStreamSource(repository.StreamSource):
1708
"""Stream data from a remote server."""
1710
def get_stream(self, search):
1711
if (self.from_repository._fallback_repositories and
1712
self.to_format._fetch_order == 'topological'):
1713
return self._real_stream(self.from_repository, search)
1714
return self.missing_parents_chain(search, [self.from_repository] +
1715
self.from_repository._fallback_repositories)
1717
def _real_stream(self, repo, search):
1718
"""Get a stream for search from repo.
1720
This never called RemoteStreamSource.get_stream, and is a heler
1721
for RemoteStreamSource._get_stream to allow getting a stream
1722
reliably whether fallback back because of old servers or trying
1723
to stream from a non-RemoteRepository (which the stacked support
1726
source = repo._get_source(self.to_format)
1727
if isinstance(source, RemoteStreamSource):
1728
return repository.StreamSource.get_stream(source, search)
1729
return source.get_stream(search)
1731
def _get_stream(self, repo, search):
1732
"""Core worker to get a stream from repo for search.
1734
This is used by both get_stream and the stacking support logic. It
1735
deliberately gets a stream for repo which does not need to be
1736
self.from_repository. In the event that repo is not Remote, or
1737
cannot do a smart stream, a fallback is made to the generic
1738
repository._get_stream() interface, via self._real_stream.
1740
In the event of stacking, streams from _get_stream will not
1741
contain all the data for search - this is normal (see get_stream).
1743
:param repo: A repository.
1744
:param search: A search.
1746
# Fallbacks may be non-smart
1747
if not isinstance(repo, RemoteRepository):
1748
return self._real_stream(repo, search)
1749
client = repo._client
1750
medium = client._medium
1751
if medium._is_remote_before((1, 13)):
1752
# streaming was added in 1.13
1753
return self._real_stream(repo, search)
1754
path = repo.bzrdir._path_for_remote_call(client)
1756
search_bytes = repo._serialise_search_result(search)
1757
response = repo._call_with_body_bytes_expecting_body(
1758
'Repository.get_stream',
1759
(path, self.to_format.network_name()), search_bytes)
1760
response_tuple, response_handler = response
1761
except errors.UnknownSmartMethod:
1762
medium._remember_remote_is_before((1,13))
1763
return self._real_stream(repo, search)
1764
if response_tuple[0] != 'ok':
1765
raise errors.UnexpectedSmartServerResponse(response_tuple)
1766
byte_stream = response_handler.read_streamed_body()
1767
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1768
if src_format.network_name() != repo._format.network_name():
1769
raise AssertionError(
1770
"Mismatched RemoteRepository and stream src %r, %r" % (
1771
src_format.network_name(), repo._format.network_name()))
1774
def missing_parents_chain(self, search, sources):
1775
"""Chain multiple streams together to handle stacking.
1777
:param search: The overall search to satisfy with streams.
1778
:param sources: A list of Repository objects to query.
1780
self.serialiser = self.to_format._serializer
1781
self.seen_revs = set()
1782
self.referenced_revs = set()
1783
# If there are heads in the search, or the key count is > 0, we are not
1785
while not search.is_empty() and len(sources) > 1:
1786
source = sources.pop(0)
1787
stream = self._get_stream(source, search)
1788
for kind, substream in stream:
1789
if kind != 'revisions':
1790
yield kind, substream
1792
yield kind, self.missing_parents_rev_handler(substream)
1793
search = search.refine(self.seen_revs, self.referenced_revs)
1794
self.seen_revs = set()
1795
self.referenced_revs = set()
1796
if not search.is_empty():
1797
for kind, stream in self._get_stream(sources[0], search):
1800
def missing_parents_rev_handler(self, substream):
1801
for content in substream:
1802
revision_bytes = content.get_bytes_as('fulltext')
1803
revision = self.serialiser.read_revision_from_string(revision_bytes)
1804
self.seen_revs.add(content.key[-1])
1805
self.referenced_revs.update(revision.parent_ids)
1809
class RemoteBranchLockableFiles(LockableFiles):
1810
"""A 'LockableFiles' implementation that talks to a smart server.
1812
This is not a public interface class.
1815
def __init__(self, bzrdir, _client):
1816
self.bzrdir = bzrdir
1817
self._client = _client
1818
self._need_find_modes = True
1819
LockableFiles.__init__(
1820
self, bzrdir.get_branch_transport(None),
1821
'lock', lockdir.LockDir)
1823
def _find_modes(self):
1824
# RemoteBranches don't let the client set the mode of control files.
1825
self._dir_mode = None
1826
self._file_mode = None
1829
class RemoteBranchFormat(branch.BranchFormat):
1831
def __init__(self, network_name=None):
1832
super(RemoteBranchFormat, self).__init__()
1833
self._matchingbzrdir = RemoteBzrDirFormat()
1834
self._matchingbzrdir.set_branch_format(self)
1835
self._custom_format = None
1836
self._network_name = network_name
1838
def __eq__(self, other):
1839
return (isinstance(other, RemoteBranchFormat) and
1840
self.__dict__ == other.__dict__)
1842
def _ensure_real(self):
1843
if self._custom_format is None:
1844
self._custom_format = branch.network_format_registry.get(
1847
def get_format_description(self):
1848
return 'Remote BZR Branch'
1850
def network_name(self):
1851
return self._network_name
1853
def open(self, a_bzrdir, ignore_fallbacks=False):
1854
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
1856
def _vfs_initialize(self, a_bzrdir):
1857
# Initialisation when using a local bzrdir object, or a non-vfs init
1858
# method is not available on the server.
1859
# self._custom_format is always set - the start of initialize ensures
1861
if isinstance(a_bzrdir, RemoteBzrDir):
1862
a_bzrdir._ensure_real()
1863
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1865
# We assume the bzrdir is parameterised; it may not be.
1866
result = self._custom_format.initialize(a_bzrdir)
1867
if (isinstance(a_bzrdir, RemoteBzrDir) and
1868
not isinstance(result, RemoteBranch)):
1869
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1872
def initialize(self, a_bzrdir):
1873
# 1) get the network name to use.
1874
if self._custom_format:
1875
network_name = self._custom_format.network_name()
1877
# Select the current bzrlib default and ask for that.
1878
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1879
reference_format = reference_bzrdir_format.get_branch_format()
1880
self._custom_format = reference_format
1881
network_name = reference_format.network_name()
1882
# Being asked to create on a non RemoteBzrDir:
1883
if not isinstance(a_bzrdir, RemoteBzrDir):
1884
return self._vfs_initialize(a_bzrdir)
1885
medium = a_bzrdir._client._medium
1886
if medium._is_remote_before((1, 13)):
1887
return self._vfs_initialize(a_bzrdir)
1888
# Creating on a remote bzr dir.
1889
# 2) try direct creation via RPC
1890
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1891
verb = 'BzrDir.create_branch'
1893
response = a_bzrdir._call(verb, path, network_name)
1894
except errors.UnknownSmartMethod:
1895
# Fallback - use vfs methods
1896
medium._remember_remote_is_before((1, 13))
1897
return self._vfs_initialize(a_bzrdir)
1898
if response[0] != 'ok':
1899
raise errors.UnexpectedSmartServerResponse(response)
1900
# Turn the response into a RemoteRepository object.
1901
format = RemoteBranchFormat(network_name=response[1])
1902
repo_format = response_tuple_to_repo_format(response[3:])
1903
if response[2] == '':
1904
repo_bzrdir = a_bzrdir
1906
repo_bzrdir = RemoteBzrDir(
1907
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1909
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1910
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1911
format=format, setup_stacking=False)
1912
# XXX: We know this is a new branch, so it must have revno 0, revid
1913
# NULL_REVISION. Creating the branch locked would make this be unable
1914
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1915
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1916
return remote_branch
1918
def make_tags(self, branch):
1920
return self._custom_format.make_tags(branch)
1922
def supports_tags(self):
1923
# Remote branches might support tags, but we won't know until we
1924
# access the real remote branch.
1926
return self._custom_format.supports_tags()
1928
def supports_stacking(self):
1930
return self._custom_format.supports_stacking()
1932
def supports_set_append_revisions_only(self):
1934
return self._custom_format.supports_set_append_revisions_only()
1937
class RemoteBranch(branch.Branch, _RpcHelper):
1938
"""Branch stored on a server accessed by HPSS RPC.
1940
At the moment most operations are mapped down to simple file operations.
1943
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1944
_client=None, format=None, setup_stacking=True):
1945
"""Create a RemoteBranch instance.
1947
:param real_branch: An optional local implementation of the branch
1948
format, usually accessing the data via the VFS.
1949
:param _client: Private parameter for testing.
1950
:param format: A RemoteBranchFormat object, None to create one
1951
automatically. If supplied it should have a network_name already
1953
:param setup_stacking: If True make an RPC call to determine the
1954
stacked (or not) status of the branch. If False assume the branch
1957
# We intentionally don't call the parent class's __init__, because it
1958
# will try to assign to self.tags, which is a property in this subclass.
1959
# And the parent's __init__ doesn't do much anyway.
1960
self.bzrdir = remote_bzrdir
1961
if _client is not None:
1962
self._client = _client
1964
self._client = remote_bzrdir._client
1965
self.repository = remote_repository
1966
if real_branch is not None:
1967
self._real_branch = real_branch
1968
# Give the remote repository the matching real repo.
1969
real_repo = self._real_branch.repository
1970
if isinstance(real_repo, RemoteRepository):
1971
real_repo._ensure_real()
1972
real_repo = real_repo._real_repository
1973
self.repository._set_real_repository(real_repo)
1974
# Give the branch the remote repository to let fast-pathing happen.
1975
self._real_branch.repository = self.repository
1977
self._real_branch = None
1978
# Fill out expected attributes of branch for bzrlib API users.
1979
self._clear_cached_state()
1980
self.base = self.bzrdir.root_transport.base
1981
self._control_files = None
1982
self._lock_mode = None
1983
self._lock_token = None
1984
self._repo_lock_token = None
1985
self._lock_count = 0
1986
self._leave_lock = False
1987
# Setup a format: note that we cannot call _ensure_real until all the
1988
# attributes above are set: This code cannot be moved higher up in this
1991
self._format = RemoteBranchFormat()
1992
if real_branch is not None:
1993
self._format._network_name = \
1994
self._real_branch._format.network_name()
1996
self._format = format
1997
if not self._format._network_name:
1998
# Did not get from open_branchV2 - old server.
2000
self._format._network_name = \
2001
self._real_branch._format.network_name()
2002
self.tags = self._format.make_tags(self)
2003
# The base class init is not called, so we duplicate this:
2004
hooks = branch.Branch.hooks['open']
2007
self._is_stacked = False
2009
self._setup_stacking()
2011
def _setup_stacking(self):
2012
# configure stacking into the remote repository, by reading it from
2015
fallback_url = self.get_stacked_on_url()
2016
except (errors.NotStacked, errors.UnstackableBranchFormat,
2017
errors.UnstackableRepositoryFormat), e:
2019
self._is_stacked = True
2020
self._activate_fallback_location(fallback_url)
2022
def _get_config(self):
2023
return RemoteBranchConfig(self)
2025
def _get_real_transport(self):
2026
# if we try vfs access, return the real branch's vfs transport
2028
return self._real_branch._transport
2030
_transport = property(_get_real_transport)
2033
return "%s(%s)" % (self.__class__.__name__, self.base)
2037
def _ensure_real(self):
2038
"""Ensure that there is a _real_branch set.
2040
Used before calls to self._real_branch.
2042
if self._real_branch is None:
2043
if not vfs.vfs_enabled():
2044
raise AssertionError('smart server vfs must be enabled '
2045
'to use vfs implementation')
2046
self.bzrdir._ensure_real()
2047
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
2048
if self.repository._real_repository is None:
2049
# Give the remote repository the matching real repo.
2050
real_repo = self._real_branch.repository
2051
if isinstance(real_repo, RemoteRepository):
2052
real_repo._ensure_real()
2053
real_repo = real_repo._real_repository
2054
self.repository._set_real_repository(real_repo)
2055
# Give the real branch the remote repository to let fast-pathing
2057
self._real_branch.repository = self.repository
2058
if self._lock_mode == 'r':
2059
self._real_branch.lock_read()
2060
elif self._lock_mode == 'w':
2061
self._real_branch.lock_write(token=self._lock_token)
2063
def _translate_error(self, err, **context):
2064
self.repository._translate_error(err, branch=self, **context)
2066
def _clear_cached_state(self):
2067
super(RemoteBranch, self)._clear_cached_state()
2068
if self._real_branch is not None:
2069
self._real_branch._clear_cached_state()
2071
def _clear_cached_state_of_remote_branch_only(self):
2072
"""Like _clear_cached_state, but doesn't clear the cache of
2075
This is useful when falling back to calling a method of
2076
self._real_branch that changes state. In that case the underlying
2077
branch changes, so we need to invalidate this RemoteBranch's cache of
2078
it. However, there's no need to invalidate the _real_branch's cache
2079
too, in fact doing so might harm performance.
2081
super(RemoteBranch, self)._clear_cached_state()
2084
def control_files(self):
2085
# Defer actually creating RemoteBranchLockableFiles until its needed,
2086
# because it triggers an _ensure_real that we otherwise might not need.
2087
if self._control_files is None:
2088
self._control_files = RemoteBranchLockableFiles(
2089
self.bzrdir, self._client)
2090
return self._control_files
2092
def _get_checkout_format(self):
2094
return self._real_branch._get_checkout_format()
2096
def get_physical_lock_status(self):
2097
"""See Branch.get_physical_lock_status()."""
2098
# should be an API call to the server, as branches must be lockable.
2100
return self._real_branch.get_physical_lock_status()
2102
def get_stacked_on_url(self):
2103
"""Get the URL this branch is stacked against.
2105
:raises NotStacked: If the branch is not stacked.
2106
:raises UnstackableBranchFormat: If the branch does not support
2108
:raises UnstackableRepositoryFormat: If the repository does not support
2112
# there may not be a repository yet, so we can't use
2113
# self._translate_error, so we can't use self._call either.
2114
response = self._client.call('Branch.get_stacked_on_url',
2115
self._remote_path())
2116
except errors.ErrorFromSmartServer, err:
2117
# there may not be a repository yet, so we can't call through
2118
# its _translate_error
2119
_translate_error(err, branch=self)
2120
except errors.UnknownSmartMethod, err:
2122
return self._real_branch.get_stacked_on_url()
2123
if response[0] != 'ok':
2124
raise errors.UnexpectedSmartServerResponse(response)
2127
def set_stacked_on_url(self, url):
2128
branch.Branch.set_stacked_on_url(self, url)
2130
self._is_stacked = False
2132
self._is_stacked = True
2134
def _vfs_get_tags_bytes(self):
2136
return self._real_branch._get_tags_bytes()
2138
def _get_tags_bytes(self):
2139
medium = self._client._medium
2140
if medium._is_remote_before((1, 13)):
2141
return self._vfs_get_tags_bytes()
2143
response = self._call('Branch.get_tags_bytes', self._remote_path())
2144
except errors.UnknownSmartMethod:
2145
medium._remember_remote_is_before((1, 13))
2146
return self._vfs_get_tags_bytes()
2149
def lock_read(self):
2150
self.repository.lock_read()
2151
if not self._lock_mode:
2152
self._lock_mode = 'r'
2153
self._lock_count = 1
2154
if self._real_branch is not None:
2155
self._real_branch.lock_read()
2157
self._lock_count += 1
2159
def _remote_lock_write(self, token):
2161
branch_token = repo_token = ''
2163
branch_token = token
2164
repo_token = self.repository.lock_write()
2165
self.repository.unlock()
2166
err_context = {'token': token}
2167
response = self._call(
2168
'Branch.lock_write', self._remote_path(), branch_token,
2169
repo_token or '', **err_context)
2170
if response[0] != 'ok':
2171
raise errors.UnexpectedSmartServerResponse(response)
2172
ok, branch_token, repo_token = response
2173
return branch_token, repo_token
2175
def lock_write(self, token=None):
2176
if not self._lock_mode:
2177
# Lock the branch and repo in one remote call.
2178
remote_tokens = self._remote_lock_write(token)
2179
self._lock_token, self._repo_lock_token = remote_tokens
2180
if not self._lock_token:
2181
raise SmartProtocolError('Remote server did not return a token!')
2182
# Tell the self.repository object that it is locked.
2183
self.repository.lock_write(
2184
self._repo_lock_token, _skip_rpc=True)
2186
if self._real_branch is not None:
2187
self._real_branch.lock_write(token=self._lock_token)
2188
if token is not None:
2189
self._leave_lock = True
2191
self._leave_lock = False
2192
self._lock_mode = 'w'
2193
self._lock_count = 1
2194
elif self._lock_mode == 'r':
2195
raise errors.ReadOnlyTransaction
2197
if token is not None:
2198
# A token was given to lock_write, and we're relocking, so
2199
# check that the given token actually matches the one we
2201
if token != self._lock_token:
2202
raise errors.TokenMismatch(token, self._lock_token)
2203
self._lock_count += 1
2204
# Re-lock the repository too.
2205
self.repository.lock_write(self._repo_lock_token)
2206
return self._lock_token or None
2208
def _set_tags_bytes(self, bytes):
2210
return self._real_branch._set_tags_bytes(bytes)
2212
def _unlock(self, branch_token, repo_token):
2213
err_context = {'token': str((branch_token, repo_token))}
2214
response = self._call(
2215
'Branch.unlock', self._remote_path(), branch_token,
2216
repo_token or '', **err_context)
2217
if response == ('ok',):
2219
raise errors.UnexpectedSmartServerResponse(response)
2223
self._lock_count -= 1
2224
if not self._lock_count:
2225
self._clear_cached_state()
2226
mode = self._lock_mode
2227
self._lock_mode = None
2228
if self._real_branch is not None:
2229
if (not self._leave_lock and mode == 'w' and
2230
self._repo_lock_token):
2231
# If this RemoteBranch will remove the physical lock
2232
# for the repository, make sure the _real_branch
2233
# doesn't do it first. (Because the _real_branch's
2234
# repository is set to be the RemoteRepository.)
2235
self._real_branch.repository.leave_lock_in_place()
2236
self._real_branch.unlock()
2238
# Only write-locked branched need to make a remote method
2239
# call to perform the unlock.
2241
if not self._lock_token:
2242
raise AssertionError('Locked, but no token!')
2243
branch_token = self._lock_token
2244
repo_token = self._repo_lock_token
2245
self._lock_token = None
2246
self._repo_lock_token = None
2247
if not self._leave_lock:
2248
self._unlock(branch_token, repo_token)
2250
self.repository.unlock()
2252
def break_lock(self):
2254
return self._real_branch.break_lock()
2256
def leave_lock_in_place(self):
2257
if not self._lock_token:
2258
raise NotImplementedError(self.leave_lock_in_place)
2259
self._leave_lock = True
2261
def dont_leave_lock_in_place(self):
2262
if not self._lock_token:
2263
raise NotImplementedError(self.dont_leave_lock_in_place)
2264
self._leave_lock = False
2266
def get_rev_id(self, revno, history=None):
2268
return _mod_revision.NULL_REVISION
2269
last_revision_info = self.last_revision_info()
2270
ok, result = self.repository.get_rev_id_for_revno(
2271
revno, last_revision_info)
2274
missing_parent = result[1]
2275
# Either the revision named by the server is missing, or its parent
2276
# is. Call get_parent_map to determine which, so that we report a
2278
parent_map = self.repository.get_parent_map([missing_parent])
2279
if missing_parent in parent_map:
2280
missing_parent = parent_map[missing_parent]
2281
raise errors.RevisionNotPresent(missing_parent, self.repository)
2283
def _last_revision_info(self):
2284
response = self._call('Branch.last_revision_info', self._remote_path())
2285
if response[0] != 'ok':
2286
raise SmartProtocolError('unexpected response code %s' % (response,))
2287
revno = int(response[1])
2288
last_revision = response[2]
2289
return (revno, last_revision)
2291
def _gen_revision_history(self):
2292
"""See Branch._gen_revision_history()."""
2293
if self._is_stacked:
2295
return self._real_branch._gen_revision_history()
2296
response_tuple, response_handler = self._call_expecting_body(
2297
'Branch.revision_history', self._remote_path())
2298
if response_tuple[0] != 'ok':
2299
raise errors.UnexpectedSmartServerResponse(response_tuple)
2300
result = response_handler.read_body_bytes().split('\x00')
2305
def _remote_path(self):
2306
return self.bzrdir._path_for_remote_call(self._client)
2308
def _set_last_revision_descendant(self, revision_id, other_branch,
2309
allow_diverged=False, allow_overwrite_descendant=False):
2310
# This performs additional work to meet the hook contract; while its
2311
# undesirable, we have to synthesise the revno to call the hook, and
2312
# not calling the hook is worse as it means changes can't be prevented.
2313
# Having calculated this though, we can't just call into
2314
# set_last_revision_info as a simple call, because there is a set_rh
2315
# hook that some folk may still be using.
2316
old_revno, old_revid = self.last_revision_info()
2317
history = self._lefthand_history(revision_id)
2318
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2319
err_context = {'other_branch': other_branch}
2320
response = self._call('Branch.set_last_revision_ex',
2321
self._remote_path(), self._lock_token, self._repo_lock_token,
2322
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2324
self._clear_cached_state()
2325
if len(response) != 3 and response[0] != 'ok':
2326
raise errors.UnexpectedSmartServerResponse(response)
2327
new_revno, new_revision_id = response[1:]
2328
self._last_revision_info_cache = new_revno, new_revision_id
2329
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2330
if self._real_branch is not None:
2331
cache = new_revno, new_revision_id
2332
self._real_branch._last_revision_info_cache = cache
2334
def _set_last_revision(self, revision_id):
2335
old_revno, old_revid = self.last_revision_info()
2336
# This performs additional work to meet the hook contract; while its
2337
# undesirable, we have to synthesise the revno to call the hook, and
2338
# not calling the hook is worse as it means changes can't be prevented.
2339
# Having calculated this though, we can't just call into
2340
# set_last_revision_info as a simple call, because there is a set_rh
2341
# hook that some folk may still be using.
2342
history = self._lefthand_history(revision_id)
2343
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2344
self._clear_cached_state()
2345
response = self._call('Branch.set_last_revision',
2346
self._remote_path(), self._lock_token, self._repo_lock_token,
2348
if response != ('ok',):
2349
raise errors.UnexpectedSmartServerResponse(response)
2350
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2353
def set_revision_history(self, rev_history):
2354
# Send just the tip revision of the history; the server will generate
2355
# the full history from that. If the revision doesn't exist in this
2356
# branch, NoSuchRevision will be raised.
2357
if rev_history == []:
2360
rev_id = rev_history[-1]
2361
self._set_last_revision(rev_id)
2362
for hook in branch.Branch.hooks['set_rh']:
2363
hook(self, rev_history)
2364
self._cache_revision_history(rev_history)
2366
def _get_parent_location(self):
2367
medium = self._client._medium
2368
if medium._is_remote_before((1, 13)):
2369
return self._vfs_get_parent_location()
2371
response = self._call('Branch.get_parent', self._remote_path())
2372
except errors.UnknownSmartMethod:
2373
medium._remember_remote_is_before((1, 13))
2374
return self._vfs_get_parent_location()
2375
if len(response) != 1:
2376
raise errors.UnexpectedSmartServerResponse(response)
2377
parent_location = response[0]
2378
if parent_location == '':
2380
return parent_location
2382
def _vfs_get_parent_location(self):
2384
return self._real_branch._get_parent_location()
2386
def _set_parent_location(self, url):
2387
medium = self._client._medium
2388
if medium._is_remote_before((1, 15)):
2389
return self._vfs_set_parent_location(url)
2391
call_url = url or ''
2392
if type(call_url) is not str:
2393
raise AssertionError('url must be a str or None (%s)' % url)
2394
response = self._call('Branch.set_parent_location',
2395
self._remote_path(), self._lock_token, self._repo_lock_token,
2397
except errors.UnknownSmartMethod:
2398
medium._remember_remote_is_before((1, 15))
2399
return self._vfs_set_parent_location(url)
2401
raise errors.UnexpectedSmartServerResponse(response)
2403
def _vfs_set_parent_location(self, url):
2405
return self._real_branch._set_parent_location(url)
2408
def pull(self, source, overwrite=False, stop_revision=None,
2410
self._clear_cached_state_of_remote_branch_only()
2412
return self._real_branch.pull(
2413
source, overwrite=overwrite, stop_revision=stop_revision,
2414
_override_hook_target=self, **kwargs)
2417
def push(self, target, overwrite=False, stop_revision=None):
2419
return self._real_branch.push(
2420
target, overwrite=overwrite, stop_revision=stop_revision,
2421
_override_hook_source_branch=self)
2423
def is_locked(self):
2424
return self._lock_count >= 1
2427
def revision_id_to_revno(self, revision_id):
2429
return self._real_branch.revision_id_to_revno(revision_id)
2432
def set_last_revision_info(self, revno, revision_id):
2433
# XXX: These should be returned by the set_last_revision_info verb
2434
old_revno, old_revid = self.last_revision_info()
2435
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2436
revision_id = ensure_null(revision_id)
2438
response = self._call('Branch.set_last_revision_info',
2439
self._remote_path(), self._lock_token, self._repo_lock_token,
2440
str(revno), revision_id)
2441
except errors.UnknownSmartMethod:
2443
self._clear_cached_state_of_remote_branch_only()
2444
self._real_branch.set_last_revision_info(revno, revision_id)
2445
self._last_revision_info_cache = revno, revision_id
2447
if response == ('ok',):
2448
self._clear_cached_state()
2449
self._last_revision_info_cache = revno, revision_id
2450
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2451
# Update the _real_branch's cache too.
2452
if self._real_branch is not None:
2453
cache = self._last_revision_info_cache
2454
self._real_branch._last_revision_info_cache = cache
2456
raise errors.UnexpectedSmartServerResponse(response)
2459
def generate_revision_history(self, revision_id, last_rev=None,
2461
medium = self._client._medium
2462
if not medium._is_remote_before((1, 6)):
2463
# Use a smart method for 1.6 and above servers
2465
self._set_last_revision_descendant(revision_id, other_branch,
2466
allow_diverged=True, allow_overwrite_descendant=True)
2468
except errors.UnknownSmartMethod:
2469
medium._remember_remote_is_before((1, 6))
2470
self._clear_cached_state_of_remote_branch_only()
2471
self.set_revision_history(self._lefthand_history(revision_id,
2472
last_rev=last_rev,other_branch=other_branch))
2474
def set_push_location(self, location):
2476
return self._real_branch.set_push_location(location)
2479
class RemoteConfig(object):
2480
"""A Config that reads and writes from smart verbs.
2482
It is a low-level object that considers config data to be name/value pairs
2483
that may be associated with a section. Assigning meaning to the these
2484
values is done at higher levels like bzrlib.config.TreeConfig.
2487
def get_option(self, name, section=None, default=None):
2488
"""Return the value associated with a named option.
2490
:param name: The name of the value
2491
:param section: The section the option is in (if any)
2492
:param default: The value to return if the value is not set
2493
:return: The value or default value
2496
configobj = self._get_configobj()
2498
section_obj = configobj
2501
section_obj = configobj[section]
2504
return section_obj.get(name, default)
2505
except errors.UnknownSmartMethod:
2506
return self._vfs_get_option(name, section, default)
2508
def _response_to_configobj(self, response):
2509
if len(response[0]) and response[0][0] != 'ok':
2510
raise errors.UnexpectedSmartServerResponse(response)
2511
lines = response[1].read_body_bytes().splitlines()
2512
return config.ConfigObj(lines, encoding='utf-8')
2515
class RemoteBranchConfig(RemoteConfig):
2516
"""A RemoteConfig for Branches."""
2518
def __init__(self, branch):
2519
self._branch = branch
2521
def _get_configobj(self):
2522
path = self._branch._remote_path()
2523
response = self._branch._client.call_expecting_body(
2524
'Branch.get_config_file', path)
2525
return self._response_to_configobj(response)
2527
def set_option(self, value, name, section=None):
2528
"""Set the value associated with a named option.
2530
:param value: The value to set
2531
:param name: The name of the value to set
2532
:param section: The section the option is in (if any)
2534
medium = self._branch._client._medium
2535
if medium._is_remote_before((1, 14)):
2536
return self._vfs_set_option(value, name, section)
2538
path = self._branch._remote_path()
2539
response = self._branch._client.call('Branch.set_config_option',
2540
path, self._branch._lock_token, self._branch._repo_lock_token,
2541
value.encode('utf8'), name, section or '')
2542
except errors.UnknownSmartMethod:
2543
medium._remember_remote_is_before((1, 14))
2544
return self._vfs_set_option(value, name, section)
2546
raise errors.UnexpectedSmartServerResponse(response)
2548
def _real_object(self):
2549
self._branch._ensure_real()
2550
return self._branch._real_branch
2552
def _vfs_set_option(self, value, name, section=None):
2553
return self._real_object()._get_config().set_option(
2554
value, name, section)
2557
class RemoteBzrDirConfig(RemoteConfig):
2558
"""A RemoteConfig for BzrDirs."""
2560
def __init__(self, bzrdir):
2561
self._bzrdir = bzrdir
2563
def _get_configobj(self):
2564
medium = self._bzrdir._client._medium
2565
verb = 'BzrDir.get_config_file'
2566
if medium._is_remote_before((1, 15)):
2567
raise errors.UnknownSmartMethod(verb)
2568
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2569
response = self._bzrdir._call_expecting_body(
2571
return self._response_to_configobj(response)
2573
def _vfs_get_option(self, name, section, default):
2574
return self._real_object()._get_config().get_option(
2575
name, section, default)
2577
def set_option(self, value, name, section=None):
2578
"""Set the value associated with a named option.
2580
:param value: The value to set
2581
:param name: The name of the value to set
2582
:param section: The section the option is in (if any)
2584
return self._real_object()._get_config().set_option(
2585
value, name, section)
2587
def _real_object(self):
2588
self._bzrdir._ensure_real()
2589
return self._bzrdir._real_bzrdir
2593
def _extract_tar(tar, to_dir):
2594
"""Extract all the contents of a tarfile object.
2596
A replacement for extractall, which is not present in python2.4
2599
tar.extract(tarinfo, to_dir)
2602
def _translate_error(err, **context):
2603
"""Translate an ErrorFromSmartServer into a more useful error.
2605
Possible context keys:
2613
If the error from the server doesn't match a known pattern, then
2614
UnknownErrorFromSmartServer is raised.
2618
return context[name]
2619
except KeyError, key_err:
2620
mutter('Missing key %r in context %r', key_err.args[0], context)
2623
"""Get the path from the context if present, otherwise use first error
2627
return context['path']
2628
except KeyError, key_err:
2630
return err.error_args[0]
2631
except IndexError, idx_err:
2633
'Missing key %r in context %r', key_err.args[0], context)
2636
if err.error_verb == 'NoSuchRevision':
2637
raise NoSuchRevision(find('branch'), err.error_args[0])
2638
elif err.error_verb == 'nosuchrevision':
2639
raise NoSuchRevision(find('repository'), err.error_args[0])
2640
elif err.error_tuple == ('nobranch',):
2641
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2642
elif err.error_verb == 'norepository':
2643
raise errors.NoRepositoryPresent(find('bzrdir'))
2644
elif err.error_verb == 'LockContention':
2645
raise errors.LockContention('(remote lock)')
2646
elif err.error_verb == 'UnlockableTransport':
2647
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2648
elif err.error_verb == 'LockFailed':
2649
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2650
elif err.error_verb == 'TokenMismatch':
2651
raise errors.TokenMismatch(find('token'), '(remote token)')
2652
elif err.error_verb == 'Diverged':
2653
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2654
elif err.error_verb == 'TipChangeRejected':
2655
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2656
elif err.error_verb == 'UnstackableBranchFormat':
2657
raise errors.UnstackableBranchFormat(*err.error_args)
2658
elif err.error_verb == 'UnstackableRepositoryFormat':
2659
raise errors.UnstackableRepositoryFormat(*err.error_args)
2660
elif err.error_verb == 'NotStacked':
2661
raise errors.NotStacked(branch=find('branch'))
2662
elif err.error_verb == 'PermissionDenied':
2664
if len(err.error_args) >= 2:
2665
extra = err.error_args[1]
2668
raise errors.PermissionDenied(path, extra=extra)
2669
elif err.error_verb == 'ReadError':
2671
raise errors.ReadError(path)
2672
elif err.error_verb == 'NoSuchFile':
2674
raise errors.NoSuchFile(path)
2675
elif err.error_verb == 'FileExists':
2676
raise errors.FileExists(err.error_args[0])
2677
elif err.error_verb == 'DirectoryNotEmpty':
2678
raise errors.DirectoryNotEmpty(err.error_args[0])
2679
elif err.error_verb == 'ShortReadvError':
2680
args = err.error_args
2681
raise errors.ShortReadvError(
2682
args[0], int(args[1]), int(args[2]), int(args[3]))
2683
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2684
encoding = str(err.error_args[0]) # encoding must always be a string
2685
val = err.error_args[1]
2686
start = int(err.error_args[2])
2687
end = int(err.error_args[3])
2688
reason = str(err.error_args[4]) # reason must always be a string
2689
if val.startswith('u:'):
2690
val = val[2:].decode('utf-8')
2691
elif val.startswith('s:'):
2692
val = val[2:].decode('base64')
2693
if err.error_verb == 'UnicodeDecodeError':
2694
raise UnicodeDecodeError(encoding, val, start, end, reason)
2695
elif err.error_verb == 'UnicodeEncodeError':
2696
raise UnicodeEncodeError(encoding, val, start, end, reason)
2697
elif err.error_verb == 'ReadOnlyError':
2698
raise errors.TransportNotPossible('readonly transport')
2699
raise errors.UnknownErrorFromSmartServer(err)