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()
1928
class RemoteBranch(branch.Branch, _RpcHelper):
1929
"""Branch stored on a server accessed by HPSS RPC.
1931
At the moment most operations are mapped down to simple file operations.
1934
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1935
_client=None, format=None, setup_stacking=True):
1936
"""Create a RemoteBranch instance.
1938
:param real_branch: An optional local implementation of the branch
1939
format, usually accessing the data via the VFS.
1940
:param _client: Private parameter for testing.
1941
:param format: A RemoteBranchFormat object, None to create one
1942
automatically. If supplied it should have a network_name already
1944
:param setup_stacking: If True make an RPC call to determine the
1945
stacked (or not) status of the branch. If False assume the branch
1948
# We intentionally don't call the parent class's __init__, because it
1949
# will try to assign to self.tags, which is a property in this subclass.
1950
# And the parent's __init__ doesn't do much anyway.
1951
self.bzrdir = remote_bzrdir
1952
if _client is not None:
1953
self._client = _client
1955
self._client = remote_bzrdir._client
1956
self.repository = remote_repository
1957
if real_branch is not None:
1958
self._real_branch = real_branch
1959
# Give the remote repository the matching real repo.
1960
real_repo = self._real_branch.repository
1961
if isinstance(real_repo, RemoteRepository):
1962
real_repo._ensure_real()
1963
real_repo = real_repo._real_repository
1964
self.repository._set_real_repository(real_repo)
1965
# Give the branch the remote repository to let fast-pathing happen.
1966
self._real_branch.repository = self.repository
1968
self._real_branch = None
1969
# Fill out expected attributes of branch for bzrlib API users.
1970
self._clear_cached_state()
1971
self.base = self.bzrdir.root_transport.base
1972
self._control_files = None
1973
self._lock_mode = None
1974
self._lock_token = None
1975
self._repo_lock_token = None
1976
self._lock_count = 0
1977
self._leave_lock = False
1978
# Setup a format: note that we cannot call _ensure_real until all the
1979
# attributes above are set: This code cannot be moved higher up in this
1982
self._format = RemoteBranchFormat()
1983
if real_branch is not None:
1984
self._format._network_name = \
1985
self._real_branch._format.network_name()
1987
self._format = format
1988
if not self._format._network_name:
1989
# Did not get from open_branchV2 - old server.
1991
self._format._network_name = \
1992
self._real_branch._format.network_name()
1993
self.tags = self._format.make_tags(self)
1994
# The base class init is not called, so we duplicate this:
1995
hooks = branch.Branch.hooks['open']
1998
self._is_stacked = False
2000
self._setup_stacking()
2002
def _setup_stacking(self):
2003
# configure stacking into the remote repository, by reading it from
2006
fallback_url = self.get_stacked_on_url()
2007
except (errors.NotStacked, errors.UnstackableBranchFormat,
2008
errors.UnstackableRepositoryFormat), e:
2010
self._is_stacked = True
2011
self._activate_fallback_location(fallback_url)
2013
def _get_config(self):
2014
return RemoteBranchConfig(self)
2016
def _get_real_transport(self):
2017
# if we try vfs access, return the real branch's vfs transport
2019
return self._real_branch._transport
2021
_transport = property(_get_real_transport)
2024
return "%s(%s)" % (self.__class__.__name__, self.base)
2028
def _ensure_real(self):
2029
"""Ensure that there is a _real_branch set.
2031
Used before calls to self._real_branch.
2033
if self._real_branch is None:
2034
if not vfs.vfs_enabled():
2035
raise AssertionError('smart server vfs must be enabled '
2036
'to use vfs implementation')
2037
self.bzrdir._ensure_real()
2038
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
2039
if self.repository._real_repository is None:
2040
# Give the remote repository the matching real repo.
2041
real_repo = self._real_branch.repository
2042
if isinstance(real_repo, RemoteRepository):
2043
real_repo._ensure_real()
2044
real_repo = real_repo._real_repository
2045
self.repository._set_real_repository(real_repo)
2046
# Give the real branch the remote repository to let fast-pathing
2048
self._real_branch.repository = self.repository
2049
if self._lock_mode == 'r':
2050
self._real_branch.lock_read()
2051
elif self._lock_mode == 'w':
2052
self._real_branch.lock_write(token=self._lock_token)
2054
def _translate_error(self, err, **context):
2055
self.repository._translate_error(err, branch=self, **context)
2057
def _clear_cached_state(self):
2058
super(RemoteBranch, self)._clear_cached_state()
2059
if self._real_branch is not None:
2060
self._real_branch._clear_cached_state()
2062
def _clear_cached_state_of_remote_branch_only(self):
2063
"""Like _clear_cached_state, but doesn't clear the cache of
2066
This is useful when falling back to calling a method of
2067
self._real_branch that changes state. In that case the underlying
2068
branch changes, so we need to invalidate this RemoteBranch's cache of
2069
it. However, there's no need to invalidate the _real_branch's cache
2070
too, in fact doing so might harm performance.
2072
super(RemoteBranch, self)._clear_cached_state()
2075
def control_files(self):
2076
# Defer actually creating RemoteBranchLockableFiles until its needed,
2077
# because it triggers an _ensure_real that we otherwise might not need.
2078
if self._control_files is None:
2079
self._control_files = RemoteBranchLockableFiles(
2080
self.bzrdir, self._client)
2081
return self._control_files
2083
def _get_checkout_format(self):
2085
return self._real_branch._get_checkout_format()
2087
def get_physical_lock_status(self):
2088
"""See Branch.get_physical_lock_status()."""
2089
# should be an API call to the server, as branches must be lockable.
2091
return self._real_branch.get_physical_lock_status()
2093
def get_stacked_on_url(self):
2094
"""Get the URL this branch is stacked against.
2096
:raises NotStacked: If the branch is not stacked.
2097
:raises UnstackableBranchFormat: If the branch does not support
2099
:raises UnstackableRepositoryFormat: If the repository does not support
2103
# there may not be a repository yet, so we can't use
2104
# self._translate_error, so we can't use self._call either.
2105
response = self._client.call('Branch.get_stacked_on_url',
2106
self._remote_path())
2107
except errors.ErrorFromSmartServer, err:
2108
# there may not be a repository yet, so we can't call through
2109
# its _translate_error
2110
_translate_error(err, branch=self)
2111
except errors.UnknownSmartMethod, err:
2113
return self._real_branch.get_stacked_on_url()
2114
if response[0] != 'ok':
2115
raise errors.UnexpectedSmartServerResponse(response)
2118
def set_stacked_on_url(self, url):
2119
branch.Branch.set_stacked_on_url(self, url)
2121
self._is_stacked = False
2123
self._is_stacked = True
2125
def _vfs_get_tags_bytes(self):
2127
return self._real_branch._get_tags_bytes()
2129
def _get_tags_bytes(self):
2130
medium = self._client._medium
2131
if medium._is_remote_before((1, 13)):
2132
return self._vfs_get_tags_bytes()
2134
response = self._call('Branch.get_tags_bytes', self._remote_path())
2135
except errors.UnknownSmartMethod:
2136
medium._remember_remote_is_before((1, 13))
2137
return self._vfs_get_tags_bytes()
2140
def lock_read(self):
2141
self.repository.lock_read()
2142
if not self._lock_mode:
2143
self._lock_mode = 'r'
2144
self._lock_count = 1
2145
if self._real_branch is not None:
2146
self._real_branch.lock_read()
2148
self._lock_count += 1
2150
def _remote_lock_write(self, token):
2152
branch_token = repo_token = ''
2154
branch_token = token
2155
repo_token = self.repository.lock_write()
2156
self.repository.unlock()
2157
err_context = {'token': token}
2158
response = self._call(
2159
'Branch.lock_write', self._remote_path(), branch_token,
2160
repo_token or '', **err_context)
2161
if response[0] != 'ok':
2162
raise errors.UnexpectedSmartServerResponse(response)
2163
ok, branch_token, repo_token = response
2164
return branch_token, repo_token
2166
def lock_write(self, token=None):
2167
if not self._lock_mode:
2168
# Lock the branch and repo in one remote call.
2169
remote_tokens = self._remote_lock_write(token)
2170
self._lock_token, self._repo_lock_token = remote_tokens
2171
if not self._lock_token:
2172
raise SmartProtocolError('Remote server did not return a token!')
2173
# Tell the self.repository object that it is locked.
2174
self.repository.lock_write(
2175
self._repo_lock_token, _skip_rpc=True)
2177
if self._real_branch is not None:
2178
self._real_branch.lock_write(token=self._lock_token)
2179
if token is not None:
2180
self._leave_lock = True
2182
self._leave_lock = False
2183
self._lock_mode = 'w'
2184
self._lock_count = 1
2185
elif self._lock_mode == 'r':
2186
raise errors.ReadOnlyTransaction
2188
if token is not None:
2189
# A token was given to lock_write, and we're relocking, so
2190
# check that the given token actually matches the one we
2192
if token != self._lock_token:
2193
raise errors.TokenMismatch(token, self._lock_token)
2194
self._lock_count += 1
2195
# Re-lock the repository too.
2196
self.repository.lock_write(self._repo_lock_token)
2197
return self._lock_token or None
2199
def _set_tags_bytes(self, bytes):
2201
return self._real_branch._set_tags_bytes(bytes)
2203
def _unlock(self, branch_token, repo_token):
2204
err_context = {'token': str((branch_token, repo_token))}
2205
response = self._call(
2206
'Branch.unlock', self._remote_path(), branch_token,
2207
repo_token or '', **err_context)
2208
if response == ('ok',):
2210
raise errors.UnexpectedSmartServerResponse(response)
2214
self._lock_count -= 1
2215
if not self._lock_count:
2216
self._clear_cached_state()
2217
mode = self._lock_mode
2218
self._lock_mode = None
2219
if self._real_branch is not None:
2220
if (not self._leave_lock and mode == 'w' and
2221
self._repo_lock_token):
2222
# If this RemoteBranch will remove the physical lock
2223
# for the repository, make sure the _real_branch
2224
# doesn't do it first. (Because the _real_branch's
2225
# repository is set to be the RemoteRepository.)
2226
self._real_branch.repository.leave_lock_in_place()
2227
self._real_branch.unlock()
2229
# Only write-locked branched need to make a remote method
2230
# call to perform the unlock.
2232
if not self._lock_token:
2233
raise AssertionError('Locked, but no token!')
2234
branch_token = self._lock_token
2235
repo_token = self._repo_lock_token
2236
self._lock_token = None
2237
self._repo_lock_token = None
2238
if not self._leave_lock:
2239
self._unlock(branch_token, repo_token)
2241
self.repository.unlock()
2243
def break_lock(self):
2245
return self._real_branch.break_lock()
2247
def leave_lock_in_place(self):
2248
if not self._lock_token:
2249
raise NotImplementedError(self.leave_lock_in_place)
2250
self._leave_lock = True
2252
def dont_leave_lock_in_place(self):
2253
if not self._lock_token:
2254
raise NotImplementedError(self.dont_leave_lock_in_place)
2255
self._leave_lock = False
2257
def get_rev_id(self, revno, history=None):
2259
return _mod_revision.NULL_REVISION
2260
last_revision_info = self.last_revision_info()
2261
ok, result = self.repository.get_rev_id_for_revno(
2262
revno, last_revision_info)
2265
missing_parent = result[1]
2266
# Either the revision named by the server is missing, or its parent
2267
# is. Call get_parent_map to determine which, so that we report a
2269
parent_map = self.repository.get_parent_map([missing_parent])
2270
if missing_parent in parent_map:
2271
missing_parent = parent_map[missing_parent]
2272
raise errors.RevisionNotPresent(missing_parent, self.repository)
2274
def _last_revision_info(self):
2275
response = self._call('Branch.last_revision_info', self._remote_path())
2276
if response[0] != 'ok':
2277
raise SmartProtocolError('unexpected response code %s' % (response,))
2278
revno = int(response[1])
2279
last_revision = response[2]
2280
return (revno, last_revision)
2282
def _gen_revision_history(self):
2283
"""See Branch._gen_revision_history()."""
2284
if self._is_stacked:
2286
return self._real_branch._gen_revision_history()
2287
response_tuple, response_handler = self._call_expecting_body(
2288
'Branch.revision_history', self._remote_path())
2289
if response_tuple[0] != 'ok':
2290
raise errors.UnexpectedSmartServerResponse(response_tuple)
2291
result = response_handler.read_body_bytes().split('\x00')
2296
def _remote_path(self):
2297
return self.bzrdir._path_for_remote_call(self._client)
2299
def _set_last_revision_descendant(self, revision_id, other_branch,
2300
allow_diverged=False, allow_overwrite_descendant=False):
2301
# This performs additional work to meet the hook contract; while its
2302
# undesirable, we have to synthesise the revno to call the hook, and
2303
# not calling the hook is worse as it means changes can't be prevented.
2304
# Having calculated this though, we can't just call into
2305
# set_last_revision_info as a simple call, because there is a set_rh
2306
# hook that some folk may still be using.
2307
old_revno, old_revid = self.last_revision_info()
2308
history = self._lefthand_history(revision_id)
2309
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2310
err_context = {'other_branch': other_branch}
2311
response = self._call('Branch.set_last_revision_ex',
2312
self._remote_path(), self._lock_token, self._repo_lock_token,
2313
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2315
self._clear_cached_state()
2316
if len(response) != 3 and response[0] != 'ok':
2317
raise errors.UnexpectedSmartServerResponse(response)
2318
new_revno, new_revision_id = response[1:]
2319
self._last_revision_info_cache = new_revno, new_revision_id
2320
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2321
if self._real_branch is not None:
2322
cache = new_revno, new_revision_id
2323
self._real_branch._last_revision_info_cache = cache
2325
def _set_last_revision(self, revision_id):
2326
old_revno, old_revid = self.last_revision_info()
2327
# This performs additional work to meet the hook contract; while its
2328
# undesirable, we have to synthesise the revno to call the hook, and
2329
# not calling the hook is worse as it means changes can't be prevented.
2330
# Having calculated this though, we can't just call into
2331
# set_last_revision_info as a simple call, because there is a set_rh
2332
# hook that some folk may still be using.
2333
history = self._lefthand_history(revision_id)
2334
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2335
self._clear_cached_state()
2336
response = self._call('Branch.set_last_revision',
2337
self._remote_path(), self._lock_token, self._repo_lock_token,
2339
if response != ('ok',):
2340
raise errors.UnexpectedSmartServerResponse(response)
2341
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2344
def set_revision_history(self, rev_history):
2345
# Send just the tip revision of the history; the server will generate
2346
# the full history from that. If the revision doesn't exist in this
2347
# branch, NoSuchRevision will be raised.
2348
if rev_history == []:
2351
rev_id = rev_history[-1]
2352
self._set_last_revision(rev_id)
2353
for hook in branch.Branch.hooks['set_rh']:
2354
hook(self, rev_history)
2355
self._cache_revision_history(rev_history)
2357
def _get_parent_location(self):
2358
medium = self._client._medium
2359
if medium._is_remote_before((1, 13)):
2360
return self._vfs_get_parent_location()
2362
response = self._call('Branch.get_parent', self._remote_path())
2363
except errors.UnknownSmartMethod:
2364
medium._remember_remote_is_before((1, 13))
2365
return self._vfs_get_parent_location()
2366
if len(response) != 1:
2367
raise errors.UnexpectedSmartServerResponse(response)
2368
parent_location = response[0]
2369
if parent_location == '':
2371
return parent_location
2373
def _vfs_get_parent_location(self):
2375
return self._real_branch._get_parent_location()
2377
def _set_parent_location(self, url):
2378
medium = self._client._medium
2379
if medium._is_remote_before((1, 15)):
2380
return self._vfs_set_parent_location(url)
2382
call_url = url or ''
2383
if type(call_url) is not str:
2384
raise AssertionError('url must be a str or None (%s)' % url)
2385
response = self._call('Branch.set_parent_location',
2386
self._remote_path(), self._lock_token, self._repo_lock_token,
2388
except errors.UnknownSmartMethod:
2389
medium._remember_remote_is_before((1, 15))
2390
return self._vfs_set_parent_location(url)
2392
raise errors.UnexpectedSmartServerResponse(response)
2394
def _vfs_set_parent_location(self, url):
2396
return self._real_branch._set_parent_location(url)
2399
def pull(self, source, overwrite=False, stop_revision=None,
2401
self._clear_cached_state_of_remote_branch_only()
2403
return self._real_branch.pull(
2404
source, overwrite=overwrite, stop_revision=stop_revision,
2405
_override_hook_target=self, **kwargs)
2408
def push(self, target, overwrite=False, stop_revision=None):
2410
return self._real_branch.push(
2411
target, overwrite=overwrite, stop_revision=stop_revision,
2412
_override_hook_source_branch=self)
2414
def is_locked(self):
2415
return self._lock_count >= 1
2418
def revision_id_to_revno(self, revision_id):
2420
return self._real_branch.revision_id_to_revno(revision_id)
2423
def set_last_revision_info(self, revno, revision_id):
2424
# XXX: These should be returned by the set_last_revision_info verb
2425
old_revno, old_revid = self.last_revision_info()
2426
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2427
revision_id = ensure_null(revision_id)
2429
response = self._call('Branch.set_last_revision_info',
2430
self._remote_path(), self._lock_token, self._repo_lock_token,
2431
str(revno), revision_id)
2432
except errors.UnknownSmartMethod:
2434
self._clear_cached_state_of_remote_branch_only()
2435
self._real_branch.set_last_revision_info(revno, revision_id)
2436
self._last_revision_info_cache = revno, revision_id
2438
if response == ('ok',):
2439
self._clear_cached_state()
2440
self._last_revision_info_cache = revno, revision_id
2441
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2442
# Update the _real_branch's cache too.
2443
if self._real_branch is not None:
2444
cache = self._last_revision_info_cache
2445
self._real_branch._last_revision_info_cache = cache
2447
raise errors.UnexpectedSmartServerResponse(response)
2450
def generate_revision_history(self, revision_id, last_rev=None,
2452
medium = self._client._medium
2453
if not medium._is_remote_before((1, 6)):
2454
# Use a smart method for 1.6 and above servers
2456
self._set_last_revision_descendant(revision_id, other_branch,
2457
allow_diverged=True, allow_overwrite_descendant=True)
2459
except errors.UnknownSmartMethod:
2460
medium._remember_remote_is_before((1, 6))
2461
self._clear_cached_state_of_remote_branch_only()
2462
self.set_revision_history(self._lefthand_history(revision_id,
2463
last_rev=last_rev,other_branch=other_branch))
2465
def set_push_location(self, location):
2467
return self._real_branch.set_push_location(location)
2470
class RemoteConfig(object):
2471
"""A Config that reads and writes from smart verbs.
2473
It is a low-level object that considers config data to be name/value pairs
2474
that may be associated with a section. Assigning meaning to the these
2475
values is done at higher levels like bzrlib.config.TreeConfig.
2478
def get_option(self, name, section=None, default=None):
2479
"""Return the value associated with a named option.
2481
:param name: The name of the value
2482
:param section: The section the option is in (if any)
2483
:param default: The value to return if the value is not set
2484
:return: The value or default value
2487
configobj = self._get_configobj()
2489
section_obj = configobj
2492
section_obj = configobj[section]
2495
return section_obj.get(name, default)
2496
except errors.UnknownSmartMethod:
2497
return self._vfs_get_option(name, section, default)
2499
def _response_to_configobj(self, response):
2500
if len(response[0]) and response[0][0] != 'ok':
2501
raise errors.UnexpectedSmartServerResponse(response)
2502
lines = response[1].read_body_bytes().splitlines()
2503
return config.ConfigObj(lines, encoding='utf-8')
2506
class RemoteBranchConfig(RemoteConfig):
2507
"""A RemoteConfig for Branches."""
2509
def __init__(self, branch):
2510
self._branch = branch
2512
def _get_configobj(self):
2513
path = self._branch._remote_path()
2514
response = self._branch._client.call_expecting_body(
2515
'Branch.get_config_file', path)
2516
return self._response_to_configobj(response)
2518
def set_option(self, value, name, section=None):
2519
"""Set the value associated with a named option.
2521
:param value: The value to set
2522
:param name: The name of the value to set
2523
:param section: The section the option is in (if any)
2525
medium = self._branch._client._medium
2526
if medium._is_remote_before((1, 14)):
2527
return self._vfs_set_option(value, name, section)
2529
path = self._branch._remote_path()
2530
response = self._branch._client.call('Branch.set_config_option',
2531
path, self._branch._lock_token, self._branch._repo_lock_token,
2532
value.encode('utf8'), name, section or '')
2533
except errors.UnknownSmartMethod:
2534
medium._remember_remote_is_before((1, 14))
2535
return self._vfs_set_option(value, name, section)
2537
raise errors.UnexpectedSmartServerResponse(response)
2539
def _real_object(self):
2540
self._branch._ensure_real()
2541
return self._branch._real_branch
2543
def _vfs_set_option(self, value, name, section=None):
2544
return self._real_object()._get_config().set_option(
2545
value, name, section)
2548
class RemoteBzrDirConfig(RemoteConfig):
2549
"""A RemoteConfig for BzrDirs."""
2551
def __init__(self, bzrdir):
2552
self._bzrdir = bzrdir
2554
def _get_configobj(self):
2555
medium = self._bzrdir._client._medium
2556
verb = 'BzrDir.get_config_file'
2557
if medium._is_remote_before((1, 15)):
2558
raise errors.UnknownSmartMethod(verb)
2559
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2560
response = self._bzrdir._call_expecting_body(
2562
return self._response_to_configobj(response)
2564
def _vfs_get_option(self, name, section, default):
2565
return self._real_object()._get_config().get_option(
2566
name, section, default)
2568
def set_option(self, value, name, section=None):
2569
"""Set the value associated with a named option.
2571
:param value: The value to set
2572
:param name: The name of the value to set
2573
:param section: The section the option is in (if any)
2575
return self._real_object()._get_config().set_option(
2576
value, name, section)
2578
def _real_object(self):
2579
self._bzrdir._ensure_real()
2580
return self._bzrdir._real_bzrdir
2584
def _extract_tar(tar, to_dir):
2585
"""Extract all the contents of a tarfile object.
2587
A replacement for extractall, which is not present in python2.4
2590
tar.extract(tarinfo, to_dir)
2593
def _translate_error(err, **context):
2594
"""Translate an ErrorFromSmartServer into a more useful error.
2596
Possible context keys:
2604
If the error from the server doesn't match a known pattern, then
2605
UnknownErrorFromSmartServer is raised.
2609
return context[name]
2610
except KeyError, key_err:
2611
mutter('Missing key %r in context %r', key_err.args[0], context)
2614
"""Get the path from the context if present, otherwise use first error
2618
return context['path']
2619
except KeyError, key_err:
2621
return err.error_args[0]
2622
except IndexError, idx_err:
2624
'Missing key %r in context %r', key_err.args[0], context)
2627
if err.error_verb == 'NoSuchRevision':
2628
raise NoSuchRevision(find('branch'), err.error_args[0])
2629
elif err.error_verb == 'nosuchrevision':
2630
raise NoSuchRevision(find('repository'), err.error_args[0])
2631
elif err.error_tuple == ('nobranch',):
2632
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2633
elif err.error_verb == 'norepository':
2634
raise errors.NoRepositoryPresent(find('bzrdir'))
2635
elif err.error_verb == 'LockContention':
2636
raise errors.LockContention('(remote lock)')
2637
elif err.error_verb == 'UnlockableTransport':
2638
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2639
elif err.error_verb == 'LockFailed':
2640
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2641
elif err.error_verb == 'TokenMismatch':
2642
raise errors.TokenMismatch(find('token'), '(remote token)')
2643
elif err.error_verb == 'Diverged':
2644
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2645
elif err.error_verb == 'TipChangeRejected':
2646
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2647
elif err.error_verb == 'UnstackableBranchFormat':
2648
raise errors.UnstackableBranchFormat(*err.error_args)
2649
elif err.error_verb == 'UnstackableRepositoryFormat':
2650
raise errors.UnstackableRepositoryFormat(*err.error_args)
2651
elif err.error_verb == 'NotStacked':
2652
raise errors.NotStacked(branch=find('branch'))
2653
elif err.error_verb == 'PermissionDenied':
2655
if len(err.error_args) >= 2:
2656
extra = err.error_args[1]
2659
raise errors.PermissionDenied(path, extra=extra)
2660
elif err.error_verb == 'ReadError':
2662
raise errors.ReadError(path)
2663
elif err.error_verb == 'NoSuchFile':
2665
raise errors.NoSuchFile(path)
2666
elif err.error_verb == 'FileExists':
2667
raise errors.FileExists(err.error_args[0])
2668
elif err.error_verb == 'DirectoryNotEmpty':
2669
raise errors.DirectoryNotEmpty(err.error_args[0])
2670
elif err.error_verb == 'ShortReadvError':
2671
args = err.error_args
2672
raise errors.ShortReadvError(
2673
args[0], int(args[1]), int(args[2]), int(args[3]))
2674
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2675
encoding = str(err.error_args[0]) # encoding must always be a string
2676
val = err.error_args[1]
2677
start = int(err.error_args[2])
2678
end = int(err.error_args[3])
2679
reason = str(err.error_args[4]) # reason must always be a string
2680
if val.startswith('u:'):
2681
val = val[2:].decode('utf-8')
2682
elif val.startswith('s:'):
2683
val = val[2:].decode('base64')
2684
if err.error_verb == 'UnicodeDecodeError':
2685
raise UnicodeDecodeError(encoding, val, start, end, reason)
2686
elif err.error_verb == 'UnicodeEncodeError':
2687
raise UnicodeEncodeError(encoding, val, start, end, reason)
2688
elif err.error_verb == 'ReadOnlyError':
2689
raise errors.TransportNotPossible('readonly transport')
2690
raise errors.UnknownErrorFromSmartServer(err)