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(self, method, args, body_bytes, **err_context):
66
return self._client.call_with_body_bytes(method, args, body_bytes)
67
except errors.ErrorFromSmartServer, err:
68
self._translate_error(err, **err_context)
70
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
73
return self._client.call_with_body_bytes_expecting_body(
74
method, args, body_bytes)
75
except errors.ErrorFromSmartServer, err:
76
self._translate_error(err, **err_context)
79
def response_tuple_to_repo_format(response):
80
"""Convert a response tuple describing a repository format to a format."""
81
format = RemoteRepositoryFormat()
82
format._rich_root_data = (response[0] == 'yes')
83
format._supports_tree_reference = (response[1] == 'yes')
84
format._supports_external_lookups = (response[2] == 'yes')
85
format._network_name = response[3]
89
# Note: RemoteBzrDirFormat is in bzrdir.py
91
class RemoteBzrDir(BzrDir, _RpcHelper):
92
"""Control directory on a remote server, accessed via bzr:// or similar."""
94
def __init__(self, transport, format, _client=None):
95
"""Construct a RemoteBzrDir.
97
:param _client: Private parameter for testing. Disables probing and the
100
BzrDir.__init__(self, transport, format)
101
# this object holds a delegated bzrdir that uses file-level operations
102
# to talk to the other side
103
self._real_bzrdir = None
104
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
105
# create_branch for details.
106
self._next_open_branch_result = None
109
medium = transport.get_smart_medium()
110
self._client = client._SmartClient(medium)
112
self._client = _client
115
path = self._path_for_remote_call(self._client)
116
response = self._call('BzrDir.open', path)
117
if response not in [('yes',), ('no',)]:
118
raise errors.UnexpectedSmartServerResponse(response)
119
if response == ('no',):
120
raise errors.NotBranchError(path=transport.base)
122
def _ensure_real(self):
123
"""Ensure that there is a _real_bzrdir set.
125
Used before calls to self._real_bzrdir.
127
if not self._real_bzrdir:
128
self._real_bzrdir = BzrDir.open_from_transport(
129
self.root_transport, _server_formats=False)
130
self._format._network_name = \
131
self._real_bzrdir._format.network_name()
133
def _translate_error(self, err, **context):
134
_translate_error(err, bzrdir=self, **context)
136
def break_lock(self):
137
# Prevent aliasing problems in the next_open_branch_result cache.
138
# See create_branch for rationale.
139
self._next_open_branch_result = None
140
return BzrDir.break_lock(self)
142
def _vfs_cloning_metadir(self, require_stacking=False):
144
return self._real_bzrdir.cloning_metadir(
145
require_stacking=require_stacking)
147
def cloning_metadir(self, require_stacking=False):
148
medium = self._client._medium
149
if medium._is_remote_before((1, 13)):
150
return self._vfs_cloning_metadir(require_stacking=require_stacking)
151
verb = 'BzrDir.cloning_metadir'
156
path = self._path_for_remote_call(self._client)
158
response = self._call(verb, path, stacking)
159
except errors.UnknownSmartMethod:
160
medium._remember_remote_is_before((1, 13))
161
return self._vfs_cloning_metadir(require_stacking=require_stacking)
162
except errors.UnknownErrorFromSmartServer, err:
163
if err.error_tuple != ('BranchReference',):
165
# We need to resolve the branch reference to determine the
166
# cloning_metadir. This causes unnecessary RPCs to open the
167
# referenced branch (and bzrdir, etc) but only when the caller
168
# didn't already resolve the branch reference.
169
referenced_branch = self.open_branch()
170
return referenced_branch.bzrdir.cloning_metadir()
171
if len(response) != 3:
172
raise errors.UnexpectedSmartServerResponse(response)
173
control_name, repo_name, branch_info = response
174
if len(branch_info) != 2:
175
raise errors.UnexpectedSmartServerResponse(response)
176
branch_ref, branch_name = branch_info
177
format = bzrdir.network_format_registry.get(control_name)
179
format.repository_format = repository.network_format_registry.get(
181
if branch_ref == 'ref':
182
# XXX: we need possible_transports here to avoid reopening the
183
# connection to the referenced location
184
ref_bzrdir = BzrDir.open(branch_name)
185
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
186
format.set_branch_format(branch_format)
187
elif branch_ref == 'branch':
189
format.set_branch_format(
190
branch.network_format_registry.get(branch_name))
192
raise errors.UnexpectedSmartServerResponse(response)
195
def create_repository(self, shared=False):
196
# as per meta1 formats - just delegate to the format object which may
198
result = self._format.repository_format.initialize(self, shared)
199
if not isinstance(result, RemoteRepository):
200
return self.open_repository()
204
def destroy_repository(self):
205
"""See BzrDir.destroy_repository"""
207
self._real_bzrdir.destroy_repository()
209
def create_branch(self):
210
# as per meta1 formats - just delegate to the format object which may
212
real_branch = self._format.get_branch_format().initialize(self)
213
if not isinstance(real_branch, RemoteBranch):
214
result = RemoteBranch(self, self.find_repository(), real_branch)
217
# BzrDir.clone_on_transport() uses the result of create_branch but does
218
# not return it to its callers; we save approximately 8% of our round
219
# trips by handing the branch we created back to the first caller to
220
# open_branch rather than probing anew. Long term we need a API in
221
# bzrdir that doesn't discard result objects (like result_branch).
223
self._next_open_branch_result = result
226
def destroy_branch(self):
227
"""See BzrDir.destroy_branch"""
229
self._real_bzrdir.destroy_branch()
230
self._next_open_branch_result = None
232
def create_workingtree(self, revision_id=None, from_branch=None):
233
raise errors.NotLocalUrl(self.transport.base)
235
def find_branch_format(self):
236
"""Find the branch 'format' for this bzrdir.
238
This might be a synthetic object for e.g. RemoteBranch and SVN.
240
b = self.open_branch()
243
def get_branch_reference(self):
244
"""See BzrDir.get_branch_reference()."""
245
response = self._get_branch_reference()
246
if response[0] == 'ref':
251
def _get_branch_reference(self):
252
path = self._path_for_remote_call(self._client)
253
medium = self._client._medium
254
if not medium._is_remote_before((1, 13)):
256
response = self._call('BzrDir.open_branchV2', path)
257
if response[0] not in ('ref', 'branch'):
258
raise errors.UnexpectedSmartServerResponse(response)
260
except errors.UnknownSmartMethod:
261
medium._remember_remote_is_before((1, 13))
262
response = self._call('BzrDir.open_branch', path)
263
if response[0] != 'ok':
264
raise errors.UnexpectedSmartServerResponse(response)
265
if response[1] != '':
266
return ('ref', response[1])
268
return ('branch', '')
270
def _get_tree_branch(self):
271
"""See BzrDir._get_tree_branch()."""
272
return None, self.open_branch()
274
def open_branch(self, _unsupported=False, ignore_fallbacks=False):
276
raise NotImplementedError('unsupported flag support not implemented yet.')
277
if self._next_open_branch_result is not None:
278
# See create_branch for details.
279
result = self._next_open_branch_result
280
self._next_open_branch_result = None
282
response = self._get_branch_reference()
283
if response[0] == 'ref':
284
# a branch reference, use the existing BranchReference logic.
285
format = BranchReferenceFormat()
286
return format.open(self, _found=True, location=response[1],
287
ignore_fallbacks=ignore_fallbacks)
288
branch_format_name = response[1]
289
if not branch_format_name:
290
branch_format_name = None
291
format = RemoteBranchFormat(network_name=branch_format_name)
292
return RemoteBranch(self, self.find_repository(), format=format,
293
setup_stacking=not ignore_fallbacks)
295
def _open_repo_v1(self, path):
296
verb = 'BzrDir.find_repository'
297
response = self._call(verb, path)
298
if response[0] != 'ok':
299
raise errors.UnexpectedSmartServerResponse(response)
300
# servers that only support the v1 method don't support external
303
repo = self._real_bzrdir.open_repository()
304
response = response + ('no', repo._format.network_name())
305
return response, repo
307
def _open_repo_v2(self, path):
308
verb = 'BzrDir.find_repositoryV2'
309
response = self._call(verb, path)
310
if response[0] != 'ok':
311
raise errors.UnexpectedSmartServerResponse(response)
313
repo = self._real_bzrdir.open_repository()
314
response = response + (repo._format.network_name(),)
315
return response, repo
317
def _open_repo_v3(self, path):
318
verb = 'BzrDir.find_repositoryV3'
319
medium = self._client._medium
320
if medium._is_remote_before((1, 13)):
321
raise errors.UnknownSmartMethod(verb)
323
response = self._call(verb, path)
324
except errors.UnknownSmartMethod:
325
medium._remember_remote_is_before((1, 13))
327
if response[0] != 'ok':
328
raise errors.UnexpectedSmartServerResponse(response)
329
return response, None
331
def open_repository(self):
332
path = self._path_for_remote_call(self._client)
334
for probe in [self._open_repo_v3, self._open_repo_v2,
337
response, real_repo = probe(path)
339
except errors.UnknownSmartMethod:
342
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
343
if response[0] != 'ok':
344
raise errors.UnexpectedSmartServerResponse(response)
345
if len(response) != 6:
346
raise SmartProtocolError('incorrect response length %s' % (response,))
347
if response[1] == '':
348
# repo is at this dir.
349
format = response_tuple_to_repo_format(response[2:])
350
# Used to support creating a real format instance when needed.
351
format._creating_bzrdir = self
352
remote_repo = RemoteRepository(self, format)
353
format._creating_repo = remote_repo
354
if real_repo is not None:
355
remote_repo._set_real_repository(real_repo)
358
raise errors.NoRepositoryPresent(self)
360
def open_workingtree(self, recommend_upgrade=True):
362
if self._real_bzrdir.has_workingtree():
363
raise errors.NotLocalUrl(self.root_transport)
365
raise errors.NoWorkingTree(self.root_transport.base)
367
def _path_for_remote_call(self, client):
368
"""Return the path to be used for this bzrdir in a remote call."""
369
return client.remote_path_from_transport(self.root_transport)
371
def get_branch_transport(self, branch_format):
373
return self._real_bzrdir.get_branch_transport(branch_format)
375
def get_repository_transport(self, repository_format):
377
return self._real_bzrdir.get_repository_transport(repository_format)
379
def get_workingtree_transport(self, workingtree_format):
381
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
383
def can_convert_format(self):
384
"""Upgrading of remote bzrdirs is not supported yet."""
387
def needs_format_conversion(self, format=None):
388
"""Upgrading of remote bzrdirs is not supported yet."""
390
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
391
% 'needs_format_conversion(format=None)')
394
def clone(self, url, revision_id=None, force_new_repo=False,
395
preserve_stacking=False):
397
return self._real_bzrdir.clone(url, revision_id=revision_id,
398
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
400
def _get_config(self):
401
return RemoteBzrDirConfig(self)
404
class RemoteRepositoryFormat(repository.RepositoryFormat):
405
"""Format for repositories accessed over a _SmartClient.
407
Instances of this repository are represented by RemoteRepository
410
The RemoteRepositoryFormat is parameterized during construction
411
to reflect the capabilities of the real, remote format. Specifically
412
the attributes rich_root_data and supports_tree_reference are set
413
on a per instance basis, and are not set (and should not be) at
416
:ivar _custom_format: If set, a specific concrete repository format that
417
will be used when initializing a repository with this
418
RemoteRepositoryFormat.
419
:ivar _creating_repo: If set, the repository object that this
420
RemoteRepositoryFormat was created for: it can be called into
421
to obtain data like the network name.
424
_matchingbzrdir = RemoteBzrDirFormat()
427
repository.RepositoryFormat.__init__(self)
428
self._custom_format = None
429
self._network_name = None
430
self._creating_bzrdir = None
431
self._supports_external_lookups = None
432
self._supports_tree_reference = None
433
self._rich_root_data = None
436
def fast_deltas(self):
438
return self._custom_format.fast_deltas
441
def rich_root_data(self):
442
if self._rich_root_data is None:
444
self._rich_root_data = self._custom_format.rich_root_data
445
return self._rich_root_data
448
def supports_external_lookups(self):
449
if self._supports_external_lookups is None:
451
self._supports_external_lookups = \
452
self._custom_format.supports_external_lookups
453
return self._supports_external_lookups
456
def supports_tree_reference(self):
457
if self._supports_tree_reference is None:
459
self._supports_tree_reference = \
460
self._custom_format.supports_tree_reference
461
return self._supports_tree_reference
463
def _vfs_initialize(self, a_bzrdir, shared):
464
"""Helper for common code in initialize."""
465
if self._custom_format:
466
# Custom format requested
467
result = self._custom_format.initialize(a_bzrdir, shared=shared)
468
elif self._creating_bzrdir is not None:
469
# Use the format that the repository we were created to back
471
prior_repo = self._creating_bzrdir.open_repository()
472
prior_repo._ensure_real()
473
result = prior_repo._real_repository._format.initialize(
474
a_bzrdir, shared=shared)
476
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
477
# support remote initialization.
478
# We delegate to a real object at this point (as RemoteBzrDir
479
# delegate to the repository format which would lead to infinite
480
# recursion if we just called a_bzrdir.create_repository.
481
a_bzrdir._ensure_real()
482
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
483
if not isinstance(result, RemoteRepository):
484
return self.open(a_bzrdir)
488
def initialize(self, a_bzrdir, shared=False):
489
# Being asked to create on a non RemoteBzrDir:
490
if not isinstance(a_bzrdir, RemoteBzrDir):
491
return self._vfs_initialize(a_bzrdir, shared)
492
medium = a_bzrdir._client._medium
493
if medium._is_remote_before((1, 13)):
494
return self._vfs_initialize(a_bzrdir, shared)
495
# Creating on a remote bzr dir.
496
# 1) get the network name to use.
497
if self._custom_format:
498
network_name = self._custom_format.network_name()
499
elif self._network_name:
500
network_name = self._network_name
502
# Select the current bzrlib default and ask for that.
503
reference_bzrdir_format = bzrdir.format_registry.get('default')()
504
reference_format = reference_bzrdir_format.repository_format
505
network_name = reference_format.network_name()
506
# 2) try direct creation via RPC
507
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
508
verb = 'BzrDir.create_repository'
514
response = a_bzrdir._call(verb, path, network_name, shared_str)
515
except errors.UnknownSmartMethod:
516
# Fallback - use vfs methods
517
medium._remember_remote_is_before((1, 13))
518
return self._vfs_initialize(a_bzrdir, shared)
520
# Turn the response into a RemoteRepository object.
521
format = response_tuple_to_repo_format(response[1:])
522
# Used to support creating a real format instance when needed.
523
format._creating_bzrdir = a_bzrdir
524
remote_repo = RemoteRepository(a_bzrdir, format)
525
format._creating_repo = remote_repo
528
def open(self, a_bzrdir):
529
if not isinstance(a_bzrdir, RemoteBzrDir):
530
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
531
return a_bzrdir.open_repository()
533
def _ensure_real(self):
534
if self._custom_format is None:
535
self._custom_format = repository.network_format_registry.get(
539
def _fetch_order(self):
541
return self._custom_format._fetch_order
544
def _fetch_uses_deltas(self):
546
return self._custom_format._fetch_uses_deltas
549
def _fetch_reconcile(self):
551
return self._custom_format._fetch_reconcile
553
def get_format_description(self):
554
return 'bzr remote repository'
556
def __eq__(self, other):
557
return self.__class__ is other.__class__
559
def check_conversion_target(self, target_format):
560
if self.rich_root_data and not target_format.rich_root_data:
561
raise errors.BadConversionTarget(
562
'Does not support rich root data.', target_format)
563
if (self.supports_tree_reference and
564
not getattr(target_format, 'supports_tree_reference', False)):
565
raise errors.BadConversionTarget(
566
'Does not support nested trees', target_format)
568
def network_name(self):
569
if self._network_name:
570
return self._network_name
571
self._creating_repo._ensure_real()
572
return self._creating_repo._real_repository._format.network_name()
575
def pack_compresses(self):
577
return self._custom_format.pack_compresses
580
def _serializer(self):
582
return self._custom_format._serializer
585
class RemoteRepository(_RpcHelper):
586
"""Repository accessed over rpc.
588
For the moment most operations are performed using local transport-backed
592
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
593
"""Create a RemoteRepository instance.
595
:param remote_bzrdir: The bzrdir hosting this repository.
596
:param format: The RemoteFormat object to use.
597
:param real_repository: If not None, a local implementation of the
598
repository logic for the repository, usually accessing the data
600
:param _client: Private testing parameter - override the smart client
601
to be used by the repository.
604
self._real_repository = real_repository
606
self._real_repository = None
607
self.bzrdir = remote_bzrdir
609
self._client = remote_bzrdir._client
611
self._client = _client
612
self._format = format
613
self._lock_mode = None
614
self._lock_token = None
616
self._leave_lock = False
617
# Cache of revision parents; misses are cached during read locks, and
618
# write locks when no _real_repository has been set.
619
self._unstacked_provider = graph.CachingParentsProvider(
620
get_parent_map=self._get_parent_map_rpc)
621
self._unstacked_provider.disable_cache()
623
# These depend on the actual remote format, so force them off for
624
# maximum compatibility. XXX: In future these should depend on the
625
# remote repository instance, but this is irrelevant until we perform
626
# reconcile via an RPC call.
627
self._reconcile_does_inventory_gc = False
628
self._reconcile_fixes_text_parents = False
629
self._reconcile_backsup_inventory = False
630
self.base = self.bzrdir.transport.base
631
# Additional places to query for data.
632
self._fallback_repositories = []
635
return "%s(%s)" % (self.__class__.__name__, self.base)
639
def abort_write_group(self, suppress_errors=False):
640
"""Complete a write group on the decorated repository.
642
Smart methods perform operations in a single step so this API
643
is not really applicable except as a compatibility thunk
644
for older plugins that don't use e.g. the CommitBuilder
647
:param suppress_errors: see Repository.abort_write_group.
650
return self._real_repository.abort_write_group(
651
suppress_errors=suppress_errors)
655
"""Decorate the real repository for now.
657
In the long term a full blown network facility is needed to avoid
658
creating a real repository object locally.
661
return self._real_repository.chk_bytes
663
def commit_write_group(self):
664
"""Complete a write group on the decorated repository.
666
Smart methods perform operations in a single step so this API
667
is not really applicable except as a compatibility thunk
668
for older plugins that don't use e.g. the CommitBuilder
672
return self._real_repository.commit_write_group()
674
def resume_write_group(self, tokens):
676
return self._real_repository.resume_write_group(tokens)
678
def suspend_write_group(self):
680
return self._real_repository.suspend_write_group()
682
def get_missing_parent_inventories(self, check_for_missing_texts=True):
684
return self._real_repository.get_missing_parent_inventories(
685
check_for_missing_texts=check_for_missing_texts)
687
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
689
return self._real_repository.get_rev_id_for_revno(
692
def get_rev_id_for_revno(self, revno, known_pair):
693
"""See Repository.get_rev_id_for_revno."""
694
path = self.bzrdir._path_for_remote_call(self._client)
696
if self._client._medium._is_remote_before((1, 17)):
697
return self._get_rev_id_for_revno_vfs(revno, known_pair)
698
response = self._call(
699
'Repository.get_rev_id_for_revno', path, revno, known_pair)
700
except errors.UnknownSmartMethod:
701
self._client._medium._remember_remote_is_before((1, 17))
702
return self._get_rev_id_for_revno_vfs(revno, known_pair)
703
if response[0] == 'ok':
704
return True, response[1]
705
elif response[0] == 'history-incomplete':
706
known_pair = response[1:3]
707
for fallback in self._fallback_repositories:
708
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
713
# Not found in any fallbacks
714
return False, known_pair
716
raise errors.UnexpectedSmartServerResponse(response)
718
def _ensure_real(self):
719
"""Ensure that there is a _real_repository set.
721
Used before calls to self._real_repository.
723
Note that _ensure_real causes many roundtrips to the server which are
724
not desirable, and prevents the use of smart one-roundtrip RPC's to
725
perform complex operations (such as accessing parent data, streaming
726
revisions etc). Adding calls to _ensure_real should only be done when
727
bringing up new functionality, adding fallbacks for smart methods that
728
require a fallback path, and never to replace an existing smart method
729
invocation. If in doubt chat to the bzr network team.
731
if self._real_repository is None:
732
if 'hpssvfs' in debug.debug_flags:
734
warning('VFS Repository access triggered\n%s',
735
''.join(traceback.format_stack()))
736
self._unstacked_provider.missing_keys.clear()
737
self.bzrdir._ensure_real()
738
self._set_real_repository(
739
self.bzrdir._real_bzrdir.open_repository())
741
def _translate_error(self, err, **context):
742
self.bzrdir._translate_error(err, repository=self, **context)
744
def find_text_key_references(self):
745
"""Find the text key references within the repository.
747
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
748
revision_ids. Each altered file-ids has the exact revision_ids that
749
altered it listed explicitly.
750
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
751
to whether they were referred to by the inventory of the
752
revision_id that they contain. The inventory texts from all present
753
revision ids are assessed to generate this report.
756
return self._real_repository.find_text_key_references()
758
def _generate_text_key_index(self):
759
"""Generate a new text key index for the repository.
761
This is an expensive function that will take considerable time to run.
763
:return: A dict mapping (file_id, revision_id) tuples to a list of
764
parents, also (file_id, revision_id) tuples.
767
return self._real_repository._generate_text_key_index()
769
def _get_revision_graph(self, revision_id):
770
"""Private method for using with old (< 1.2) servers to fallback."""
771
if revision_id is None:
773
elif revision.is_null(revision_id):
776
path = self.bzrdir._path_for_remote_call(self._client)
777
response = self._call_expecting_body(
778
'Repository.get_revision_graph', path, revision_id)
779
response_tuple, response_handler = response
780
if response_tuple[0] != 'ok':
781
raise errors.UnexpectedSmartServerResponse(response_tuple)
782
coded = response_handler.read_body_bytes()
784
# no revisions in this repository!
786
lines = coded.split('\n')
789
d = tuple(line.split())
790
revision_graph[d[0]] = d[1:]
792
return revision_graph
795
"""See Repository._get_sink()."""
796
return RemoteStreamSink(self)
798
def _get_source(self, to_format):
799
"""Return a source for streaming from this repository."""
800
return RemoteStreamSource(self, to_format)
803
def has_revision(self, revision_id):
804
"""True if this repository has a copy of the revision."""
805
# Copy of bzrlib.repository.Repository.has_revision
806
return revision_id in self.has_revisions((revision_id,))
809
def has_revisions(self, revision_ids):
810
"""Probe to find out the presence of multiple revisions.
812
:param revision_ids: An iterable of revision_ids.
813
:return: A set of the revision_ids that were present.
815
# Copy of bzrlib.repository.Repository.has_revisions
816
parent_map = self.get_parent_map(revision_ids)
817
result = set(parent_map)
818
if _mod_revision.NULL_REVISION in revision_ids:
819
result.add(_mod_revision.NULL_REVISION)
822
def has_same_location(self, other):
823
return (self.__class__ is other.__class__ and
824
self.bzrdir.transport.base == other.bzrdir.transport.base)
826
def get_graph(self, other_repository=None):
827
"""Return the graph for this repository format"""
828
parents_provider = self._make_parents_provider(other_repository)
829
return graph.Graph(parents_provider)
831
def gather_stats(self, revid=None, committers=None):
832
"""See Repository.gather_stats()."""
833
path = self.bzrdir._path_for_remote_call(self._client)
834
# revid can be None to indicate no revisions, not just NULL_REVISION
835
if revid is None or revision.is_null(revid):
839
if committers is None or not committers:
840
fmt_committers = 'no'
842
fmt_committers = 'yes'
843
response_tuple, response_handler = self._call_expecting_body(
844
'Repository.gather_stats', path, fmt_revid, fmt_committers)
845
if response_tuple[0] != 'ok':
846
raise errors.UnexpectedSmartServerResponse(response_tuple)
848
body = response_handler.read_body_bytes()
850
for line in body.split('\n'):
853
key, val_text = line.split(':')
854
if key in ('revisions', 'size', 'committers'):
855
result[key] = int(val_text)
856
elif key in ('firstrev', 'latestrev'):
857
values = val_text.split(' ')[1:]
858
result[key] = (float(values[0]), long(values[1]))
862
def find_branches(self, using=False):
863
"""See Repository.find_branches()."""
864
# should be an API call to the server.
866
return self._real_repository.find_branches(using=using)
868
def get_physical_lock_status(self):
869
"""See Repository.get_physical_lock_status()."""
870
# should be an API call to the server.
872
return self._real_repository.get_physical_lock_status()
874
def is_in_write_group(self):
875
"""Return True if there is an open write group.
877
write groups are only applicable locally for the smart server..
879
if self._real_repository:
880
return self._real_repository.is_in_write_group()
883
return self._lock_count >= 1
886
"""See Repository.is_shared()."""
887
path = self.bzrdir._path_for_remote_call(self._client)
888
response = self._call('Repository.is_shared', path)
889
if response[0] not in ('yes', 'no'):
890
raise SmartProtocolError('unexpected response code %s' % (response,))
891
return response[0] == 'yes'
893
def is_write_locked(self):
894
return self._lock_mode == 'w'
897
# wrong eventually - want a local lock cache context
898
if not self._lock_mode:
899
self._lock_mode = 'r'
901
self._unstacked_provider.enable_cache(cache_misses=True)
902
if self._real_repository is not None:
903
self._real_repository.lock_read()
904
for repo in self._fallback_repositories:
907
self._lock_count += 1
909
def _remote_lock_write(self, token):
910
path = self.bzrdir._path_for_remote_call(self._client)
913
err_context = {'token': token}
914
response = self._call('Repository.lock_write', path, token,
916
if response[0] == 'ok':
920
raise errors.UnexpectedSmartServerResponse(response)
922
def lock_write(self, token=None, _skip_rpc=False):
923
if not self._lock_mode:
925
if self._lock_token is not None:
926
if token != self._lock_token:
927
raise errors.TokenMismatch(token, self._lock_token)
928
self._lock_token = token
930
self._lock_token = self._remote_lock_write(token)
931
# if self._lock_token is None, then this is something like packs or
932
# svn where we don't get to lock the repo, or a weave style repository
933
# where we cannot lock it over the wire and attempts to do so will
935
if self._real_repository is not None:
936
self._real_repository.lock_write(token=self._lock_token)
937
if token is not None:
938
self._leave_lock = True
940
self._leave_lock = False
941
self._lock_mode = 'w'
943
cache_misses = self._real_repository is None
944
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
945
for repo in self._fallback_repositories:
946
# Writes don't affect fallback repos
948
elif self._lock_mode == 'r':
949
raise errors.ReadOnlyError(self)
951
self._lock_count += 1
952
return self._lock_token or None
954
def leave_lock_in_place(self):
955
if not self._lock_token:
956
raise NotImplementedError(self.leave_lock_in_place)
957
self._leave_lock = True
959
def dont_leave_lock_in_place(self):
960
if not self._lock_token:
961
raise NotImplementedError(self.dont_leave_lock_in_place)
962
self._leave_lock = False
964
def _set_real_repository(self, repository):
965
"""Set the _real_repository for this repository.
967
:param repository: The repository to fallback to for non-hpss
968
implemented operations.
970
if self._real_repository is not None:
971
# Replacing an already set real repository.
972
# We cannot do this [currently] if the repository is locked -
973
# synchronised state might be lost.
975
raise AssertionError('_real_repository is already set')
976
if isinstance(repository, RemoteRepository):
977
raise AssertionError()
978
self._real_repository = repository
979
# three code paths happen here:
980
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
981
# up stacking. In this case self._fallback_repositories is [], and the
982
# real repo is already setup. Preserve the real repo and
983
# RemoteRepository.add_fallback_repository will avoid adding
985
# 2) new servers, RemoteBranch.open() sets up stacking, and when
986
# ensure_real is triggered from a branch, the real repository to
987
# set already has a matching list with separate instances, but
988
# as they are also RemoteRepositories we don't worry about making the
989
# lists be identical.
990
# 3) new servers, RemoteRepository.ensure_real is triggered before
991
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
992
# and need to populate it.
993
if (self._fallback_repositories and
994
len(self._real_repository._fallback_repositories) !=
995
len(self._fallback_repositories)):
996
if len(self._real_repository._fallback_repositories):
997
raise AssertionError(
998
"cannot cleanly remove existing _fallback_repositories")
999
for fb in self._fallback_repositories:
1000
self._real_repository.add_fallback_repository(fb)
1001
if self._lock_mode == 'w':
1002
# if we are already locked, the real repository must be able to
1003
# acquire the lock with our token.
1004
self._real_repository.lock_write(self._lock_token)
1005
elif self._lock_mode == 'r':
1006
self._real_repository.lock_read()
1008
def start_write_group(self):
1009
"""Start a write group on the decorated repository.
1011
Smart methods perform operations in a single step so this API
1012
is not really applicable except as a compatibility thunk
1013
for older plugins that don't use e.g. the CommitBuilder
1017
return self._real_repository.start_write_group()
1019
def _unlock(self, token):
1020
path = self.bzrdir._path_for_remote_call(self._client)
1022
# with no token the remote repository is not persistently locked.
1024
err_context = {'token': token}
1025
response = self._call('Repository.unlock', path, token,
1027
if response == ('ok',):
1030
raise errors.UnexpectedSmartServerResponse(response)
1033
if not self._lock_count:
1034
raise errors.LockNotHeld(self)
1035
self._lock_count -= 1
1036
if self._lock_count > 0:
1038
self._unstacked_provider.disable_cache()
1039
old_mode = self._lock_mode
1040
self._lock_mode = None
1042
# The real repository is responsible at present for raising an
1043
# exception if it's in an unfinished write group. However, it
1044
# normally will *not* actually remove the lock from disk - that's
1045
# done by the server on receiving the Repository.unlock call.
1046
# This is just to let the _real_repository stay up to date.
1047
if self._real_repository is not None:
1048
self._real_repository.unlock()
1050
# The rpc-level lock should be released even if there was a
1051
# problem releasing the vfs-based lock.
1053
# Only write-locked repositories need to make a remote method
1054
# call to perform the unlock.
1055
old_token = self._lock_token
1056
self._lock_token = None
1057
if not self._leave_lock:
1058
self._unlock(old_token)
1059
# Fallbacks are always 'lock_read()' so we don't pay attention to
1061
for repo in self._fallback_repositories:
1064
def break_lock(self):
1065
# should hand off to the network
1067
return self._real_repository.break_lock()
1069
def _get_tarball(self, compression):
1070
"""Return a TemporaryFile containing a repository tarball.
1072
Returns None if the server does not support sending tarballs.
1075
path = self.bzrdir._path_for_remote_call(self._client)
1077
response, protocol = self._call_expecting_body(
1078
'Repository.tarball', path, compression)
1079
except errors.UnknownSmartMethod:
1080
protocol.cancel_read_body()
1082
if response[0] == 'ok':
1083
# Extract the tarball and return it
1084
t = tempfile.NamedTemporaryFile()
1085
# TODO: rpc layer should read directly into it...
1086
t.write(protocol.read_body_bytes())
1089
raise errors.UnexpectedSmartServerResponse(response)
1091
def sprout(self, to_bzrdir, revision_id=None):
1092
# TODO: Option to control what format is created?
1094
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1096
dest_repo.fetch(self, revision_id=revision_id)
1099
### These methods are just thin shims to the VFS object for now.
1101
def revision_tree(self, revision_id):
1103
return self._real_repository.revision_tree(revision_id)
1105
def get_serializer_format(self):
1107
return self._real_repository.get_serializer_format()
1109
def get_commit_builder(self, branch, parents, config, timestamp=None,
1110
timezone=None, committer=None, revprops=None,
1112
# FIXME: It ought to be possible to call this without immediately
1113
# triggering _ensure_real. For now it's the easiest thing to do.
1115
real_repo = self._real_repository
1116
builder = real_repo.get_commit_builder(branch, parents,
1117
config, timestamp=timestamp, timezone=timezone,
1118
committer=committer, revprops=revprops, revision_id=revision_id)
1121
def add_fallback_repository(self, repository):
1122
"""Add a repository to use for looking up data not held locally.
1124
:param repository: A repository.
1126
if not self._format.supports_external_lookups:
1127
raise errors.UnstackableRepositoryFormat(
1128
self._format.network_name(), self.base)
1129
# We need to accumulate additional repositories here, to pass them in
1132
if self.is_locked():
1133
# We will call fallback.unlock() when we transition to the unlocked
1134
# state, so always add a lock here. If a caller passes us a locked
1135
# repository, they are responsible for unlocking it later.
1136
repository.lock_read()
1137
self._fallback_repositories.append(repository)
1138
# If self._real_repository was parameterised already (e.g. because a
1139
# _real_branch had its get_stacked_on_url method called), then the
1140
# repository to be added may already be in the _real_repositories list.
1141
if self._real_repository is not None:
1142
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1143
self._real_repository._fallback_repositories]
1144
if repository.bzrdir.root_transport.base not in fallback_locations:
1145
self._real_repository.add_fallback_repository(repository)
1147
def add_inventory(self, revid, inv, parents):
1149
return self._real_repository.add_inventory(revid, inv, parents)
1151
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1154
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1155
delta, new_revision_id, parents)
1157
def add_revision(self, rev_id, rev, inv=None, config=None):
1159
return self._real_repository.add_revision(
1160
rev_id, rev, inv=inv, config=config)
1163
def get_inventory(self, revision_id):
1165
return self._real_repository.get_inventory(revision_id)
1167
def iter_inventories(self, revision_ids):
1169
return self._real_repository.iter_inventories(revision_ids)
1172
def get_revision(self, revision_id):
1174
return self._real_repository.get_revision(revision_id)
1176
def get_transaction(self):
1178
return self._real_repository.get_transaction()
1181
def clone(self, a_bzrdir, revision_id=None):
1183
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1185
def make_working_trees(self):
1186
"""See Repository.make_working_trees"""
1188
return self._real_repository.make_working_trees()
1190
def refresh_data(self):
1191
"""Re-read any data needed to to synchronise with disk.
1193
This method is intended to be called after another repository instance
1194
(such as one used by a smart server) has inserted data into the
1195
repository. It may not be called during a write group, but may be
1196
called at any other time.
1198
if self.is_in_write_group():
1199
raise errors.InternalBzrError(
1200
"May not refresh_data while in a write group.")
1201
if self._real_repository is not None:
1202
self._real_repository.refresh_data()
1204
def revision_ids_to_search_result(self, result_set):
1205
"""Convert a set of revision ids to a graph SearchResult."""
1206
result_parents = set()
1207
for parents in self.get_graph().get_parent_map(
1208
result_set).itervalues():
1209
result_parents.update(parents)
1210
included_keys = result_set.intersection(result_parents)
1211
start_keys = result_set.difference(included_keys)
1212
exclude_keys = result_parents.difference(result_set)
1213
result = graph.SearchResult(start_keys, exclude_keys,
1214
len(result_set), result_set)
1218
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1219
"""Return the revision ids that other has that this does not.
1221
These are returned in topological order.
1223
revision_id: only return revision ids included by revision_id.
1225
return repository.InterRepository.get(
1226
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1228
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1230
# No base implementation to use as RemoteRepository is not a subclass
1231
# of Repository; so this is a copy of Repository.fetch().
1232
if fetch_spec is not None and revision_id is not None:
1233
raise AssertionError(
1234
"fetch_spec and revision_id are mutually exclusive.")
1235
if self.is_in_write_group():
1236
raise errors.InternalBzrError(
1237
"May not fetch while in a write group.")
1238
# fast path same-url fetch operations
1239
if self.has_same_location(source) and fetch_spec is None:
1240
# check that last_revision is in 'from' and then return a
1242
if (revision_id is not None and
1243
not revision.is_null(revision_id)):
1244
self.get_revision(revision_id)
1246
# if there is no specific appropriate InterRepository, this will get
1247
# the InterRepository base class, which raises an
1248
# IncompatibleRepositories when asked to fetch.
1249
inter = repository.InterRepository.get(source, self)
1250
return inter.fetch(revision_id=revision_id, pb=pb,
1251
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1253
def create_bundle(self, target, base, fileobj, format=None):
1255
self._real_repository.create_bundle(target, base, fileobj, format)
1258
def get_ancestry(self, revision_id, topo_sorted=True):
1260
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1262
def fileids_altered_by_revision_ids(self, revision_ids):
1264
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1266
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1268
return self._real_repository._get_versioned_file_checker(
1269
revisions, revision_versions_cache)
1271
def iter_files_bytes(self, desired_files):
1272
"""See Repository.iter_file_bytes.
1275
return self._real_repository.iter_files_bytes(desired_files)
1277
def get_parent_map(self, revision_ids):
1278
"""See bzrlib.Graph.get_parent_map()."""
1279
return self._make_parents_provider().get_parent_map(revision_ids)
1281
def _get_parent_map_rpc(self, keys):
1282
"""Helper for get_parent_map that performs the RPC."""
1283
medium = self._client._medium
1284
if medium._is_remote_before((1, 2)):
1285
# We already found out that the server can't understand
1286
# Repository.get_parent_map requests, so just fetch the whole
1289
# Note that this reads the whole graph, when only some keys are
1290
# wanted. On this old server there's no way (?) to get them all
1291
# in one go, and the user probably will have seen a warning about
1292
# the server being old anyhow.
1293
rg = self._get_revision_graph(None)
1294
# There is an API discrepancy between get_parent_map and
1295
# get_revision_graph. Specifically, a "key:()" pair in
1296
# get_revision_graph just means a node has no parents. For
1297
# "get_parent_map" it means the node is a ghost. So fix up the
1298
# graph to correct this.
1299
# https://bugs.launchpad.net/bzr/+bug/214894
1300
# There is one other "bug" which is that ghosts in
1301
# get_revision_graph() are not returned at all. But we won't worry
1302
# about that for now.
1303
for node_id, parent_ids in rg.iteritems():
1304
if parent_ids == ():
1305
rg[node_id] = (NULL_REVISION,)
1306
rg[NULL_REVISION] = ()
1311
raise ValueError('get_parent_map(None) is not valid')
1312
if NULL_REVISION in keys:
1313
keys.discard(NULL_REVISION)
1314
found_parents = {NULL_REVISION:()}
1316
return found_parents
1319
# TODO(Needs analysis): We could assume that the keys being requested
1320
# from get_parent_map are in a breadth first search, so typically they
1321
# will all be depth N from some common parent, and we don't have to
1322
# have the server iterate from the root parent, but rather from the
1323
# keys we're searching; and just tell the server the keyspace we
1324
# already have; but this may be more traffic again.
1326
# Transform self._parents_map into a search request recipe.
1327
# TODO: Manage this incrementally to avoid covering the same path
1328
# repeatedly. (The server will have to on each request, but the less
1329
# work done the better).
1331
# Negative caching notes:
1332
# new server sends missing when a request including the revid
1333
# 'include-missing:' is present in the request.
1334
# missing keys are serialised as missing:X, and we then call
1335
# provider.note_missing(X) for-all X
1336
parents_map = self._unstacked_provider.get_cached_map()
1337
if parents_map is None:
1338
# Repository is not locked, so there's no cache.
1340
# start_set is all the keys in the cache
1341
start_set = set(parents_map)
1342
# result set is all the references to keys in the cache
1343
result_parents = set()
1344
for parents in parents_map.itervalues():
1345
result_parents.update(parents)
1346
stop_keys = result_parents.difference(start_set)
1347
# We don't need to send ghosts back to the server as a position to
1349
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1350
key_count = len(parents_map)
1351
if (NULL_REVISION in result_parents
1352
and NULL_REVISION in self._unstacked_provider.missing_keys):
1353
# If we pruned NULL_REVISION from the stop_keys because it's also
1354
# in our cache of "missing" keys we need to increment our key count
1355
# by 1, because the reconsitituted SearchResult on the server will
1356
# still consider NULL_REVISION to be an included key.
1358
included_keys = start_set.intersection(result_parents)
1359
start_set.difference_update(included_keys)
1360
recipe = ('manual', start_set, stop_keys, key_count)
1361
body = self._serialise_search_recipe(recipe)
1362
path = self.bzrdir._path_for_remote_call(self._client)
1364
if type(key) is not str:
1366
"key %r not a plain string" % (key,))
1367
verb = 'Repository.get_parent_map'
1368
args = (path, 'include-missing:') + tuple(keys)
1370
response = self._call_with_body_bytes_expecting_body(
1372
except errors.UnknownSmartMethod:
1373
# Server does not support this method, so get the whole graph.
1374
# Worse, we have to force a disconnection, because the server now
1375
# doesn't realise it has a body on the wire to consume, so the
1376
# only way to recover is to abandon the connection.
1378
'Server is too old for fast get_parent_map, reconnecting. '
1379
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1381
# To avoid having to disconnect repeatedly, we keep track of the
1382
# fact the server doesn't understand remote methods added in 1.2.
1383
medium._remember_remote_is_before((1, 2))
1384
# Recurse just once and we should use the fallback code.
1385
return self._get_parent_map_rpc(keys)
1386
response_tuple, response_handler = response
1387
if response_tuple[0] not in ['ok']:
1388
response_handler.cancel_read_body()
1389
raise errors.UnexpectedSmartServerResponse(response_tuple)
1390
if response_tuple[0] == 'ok':
1391
coded = bz2.decompress(response_handler.read_body_bytes())
1393
# no revisions found
1395
lines = coded.split('\n')
1398
d = tuple(line.split())
1400
revision_graph[d[0]] = d[1:]
1403
if d[0].startswith('missing:'):
1405
self._unstacked_provider.note_missing_key(revid)
1407
# no parents - so give the Graph result
1409
revision_graph[d[0]] = (NULL_REVISION,)
1410
return revision_graph
1413
def get_signature_text(self, revision_id):
1415
return self._real_repository.get_signature_text(revision_id)
1418
def get_inventory_xml(self, revision_id):
1420
return self._real_repository.get_inventory_xml(revision_id)
1422
def deserialise_inventory(self, revision_id, xml):
1424
return self._real_repository.deserialise_inventory(revision_id, xml)
1426
def reconcile(self, other=None, thorough=False):
1428
return self._real_repository.reconcile(other=other, thorough=thorough)
1430
def all_revision_ids(self):
1432
return self._real_repository.all_revision_ids()
1435
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1437
return self._real_repository.get_deltas_for_revisions(revisions,
1438
specific_fileids=specific_fileids)
1441
def get_revision_delta(self, revision_id, specific_fileids=None):
1443
return self._real_repository.get_revision_delta(revision_id,
1444
specific_fileids=specific_fileids)
1447
def revision_trees(self, revision_ids):
1449
return self._real_repository.revision_trees(revision_ids)
1452
def get_revision_reconcile(self, revision_id):
1454
return self._real_repository.get_revision_reconcile(revision_id)
1457
def check(self, revision_ids=None):
1459
return self._real_repository.check(revision_ids=revision_ids)
1461
def copy_content_into(self, destination, revision_id=None):
1463
return self._real_repository.copy_content_into(
1464
destination, revision_id=revision_id)
1466
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1467
# get a tarball of the remote repository, and copy from that into the
1469
from bzrlib import osutils
1471
# TODO: Maybe a progress bar while streaming the tarball?
1472
note("Copying repository content as tarball...")
1473
tar_file = self._get_tarball('bz2')
1474
if tar_file is None:
1476
destination = to_bzrdir.create_repository()
1478
tar = tarfile.open('repository', fileobj=tar_file,
1480
tmpdir = osutils.mkdtemp()
1482
_extract_tar(tar, tmpdir)
1483
tmp_bzrdir = BzrDir.open(tmpdir)
1484
tmp_repo = tmp_bzrdir.open_repository()
1485
tmp_repo.copy_content_into(destination, revision_id)
1487
osutils.rmtree(tmpdir)
1491
# TODO: Suggestion from john: using external tar is much faster than
1492
# python's tarfile library, but it may not work on windows.
1495
def inventories(self):
1496
"""Decorate the real repository for now.
1498
In the long term a full blown network facility is needed to
1499
avoid creating a real repository object locally.
1502
return self._real_repository.inventories
1505
def pack(self, hint=None):
1506
"""Compress the data within the repository.
1508
This is not currently implemented within the smart server.
1511
return self._real_repository.pack(hint=hint)
1514
def revisions(self):
1515
"""Decorate the real repository for now.
1517
In the short term this should become a real object to intercept graph
1520
In the long term a full blown network facility is needed.
1523
return self._real_repository.revisions
1525
def set_make_working_trees(self, new_value):
1527
new_value_str = "True"
1529
new_value_str = "False"
1530
path = self.bzrdir._path_for_remote_call(self._client)
1532
response = self._call(
1533
'Repository.set_make_working_trees', path, new_value_str)
1534
except errors.UnknownSmartMethod:
1536
self._real_repository.set_make_working_trees(new_value)
1538
if response[0] != 'ok':
1539
raise errors.UnexpectedSmartServerResponse(response)
1542
def signatures(self):
1543
"""Decorate the real repository for now.
1545
In the long term a full blown network facility is needed to avoid
1546
creating a real repository object locally.
1549
return self._real_repository.signatures
1552
def sign_revision(self, revision_id, gpg_strategy):
1554
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1558
"""Decorate the real repository for now.
1560
In the long term a full blown network facility is needed to avoid
1561
creating a real repository object locally.
1564
return self._real_repository.texts
1567
def get_revisions(self, revision_ids):
1569
return self._real_repository.get_revisions(revision_ids)
1571
def supports_rich_root(self):
1572
return self._format.rich_root_data
1574
def iter_reverse_revision_history(self, revision_id):
1576
return self._real_repository.iter_reverse_revision_history(revision_id)
1579
def _serializer(self):
1580
return self._format._serializer
1582
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1584
return self._real_repository.store_revision_signature(
1585
gpg_strategy, plaintext, revision_id)
1587
def add_signature_text(self, revision_id, signature):
1589
return self._real_repository.add_signature_text(revision_id, signature)
1591
def has_signature_for_revision_id(self, revision_id):
1593
return self._real_repository.has_signature_for_revision_id(revision_id)
1595
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1597
return self._real_repository.item_keys_introduced_by(revision_ids,
1598
_files_pb=_files_pb)
1600
def revision_graph_can_have_wrong_parents(self):
1601
# The answer depends on the remote repo format.
1603
return self._real_repository.revision_graph_can_have_wrong_parents()
1605
def _find_inconsistent_revision_parents(self):
1607
return self._real_repository._find_inconsistent_revision_parents()
1609
def _check_for_inconsistent_revision_parents(self):
1611
return self._real_repository._check_for_inconsistent_revision_parents()
1613
def _make_parents_provider(self, other=None):
1614
providers = [self._unstacked_provider]
1615
if other is not None:
1616
providers.insert(0, other)
1617
providers.extend(r._make_parents_provider() for r in
1618
self._fallback_repositories)
1619
return graph.StackedParentsProvider(providers)
1621
def _serialise_search_recipe(self, recipe):
1622
"""Serialise a graph search recipe.
1624
:param recipe: A search recipe (start, stop, count).
1625
:return: Serialised bytes.
1627
start_keys = ' '.join(recipe[1])
1628
stop_keys = ' '.join(recipe[2])
1629
count = str(recipe[3])
1630
return '\n'.join((start_keys, stop_keys, count))
1632
def _serialise_search_result(self, search_result):
1633
if isinstance(search_result, graph.PendingAncestryResult):
1634
parts = ['ancestry-of']
1635
parts.extend(search_result.heads)
1637
recipe = search_result.get_recipe()
1638
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1639
return '\n'.join(parts)
1642
path = self.bzrdir._path_for_remote_call(self._client)
1644
response = self._call('PackRepository.autopack', path)
1645
except errors.UnknownSmartMethod:
1647
self._real_repository._pack_collection.autopack()
1650
if response[0] != 'ok':
1651
raise errors.UnexpectedSmartServerResponse(response)
1654
class RemoteStreamSink(repository.StreamSink):
1656
def _insert_real(self, stream, src_format, resume_tokens):
1657
self.target_repo._ensure_real()
1658
sink = self.target_repo._real_repository._get_sink()
1659
result = sink.insert_stream(stream, src_format, resume_tokens)
1661
self.target_repo.autopack()
1664
def insert_stream(self, stream, src_format, resume_tokens):
1665
target = self.target_repo
1666
target._unstacked_provider.missing_keys.clear()
1667
if target._lock_token:
1668
verb = 'Repository.insert_stream_locked'
1669
extra_args = (target._lock_token or '',)
1670
required_version = (1, 14)
1672
verb = 'Repository.insert_stream'
1674
required_version = (1, 13)
1675
client = target._client
1676
medium = client._medium
1677
if medium._is_remote_before(required_version):
1678
# No possible way this can work.
1679
return self._insert_real(stream, src_format, resume_tokens)
1680
path = target.bzrdir._path_for_remote_call(client)
1681
if not resume_tokens:
1682
# XXX: Ugly but important for correctness, *will* be fixed during
1683
# 1.13 cycle. Pushing a stream that is interrupted results in a
1684
# fallback to the _real_repositories sink *with a partial stream*.
1685
# Thats bad because we insert less data than bzr expected. To avoid
1686
# this we do a trial push to make sure the verb is accessible, and
1687
# do not fallback when actually pushing the stream. A cleanup patch
1688
# is going to look at rewinding/restarting the stream/partial
1690
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1692
response = client.call_with_body_stream(
1693
(verb, path, '') + extra_args, byte_stream)
1694
except errors.UnknownSmartMethod:
1695
medium._remember_remote_is_before(required_version)
1696
return self._insert_real(stream, src_format, resume_tokens)
1697
byte_stream = smart_repo._stream_to_byte_stream(
1699
resume_tokens = ' '.join(resume_tokens)
1700
response = client.call_with_body_stream(
1701
(verb, path, resume_tokens) + extra_args, byte_stream)
1702
if response[0][0] not in ('ok', 'missing-basis'):
1703
raise errors.UnexpectedSmartServerResponse(response)
1704
if response[0][0] == 'missing-basis':
1705
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1706
resume_tokens = tokens
1707
return resume_tokens, set(missing_keys)
1709
self.target_repo.refresh_data()
1713
class RemoteStreamSource(repository.StreamSource):
1714
"""Stream data from a remote server."""
1716
def get_stream(self, search):
1717
if (self.from_repository._fallback_repositories and
1718
self.to_format._fetch_order == 'topological'):
1719
return self._real_stream(self.from_repository, search)
1720
return self.missing_parents_chain(search, [self.from_repository] +
1721
self.from_repository._fallback_repositories)
1723
def _real_stream(self, repo, search):
1724
"""Get a stream for search from repo.
1726
This never called RemoteStreamSource.get_stream, and is a heler
1727
for RemoteStreamSource._get_stream to allow getting a stream
1728
reliably whether fallback back because of old servers or trying
1729
to stream from a non-RemoteRepository (which the stacked support
1732
source = repo._get_source(self.to_format)
1733
if isinstance(source, RemoteStreamSource):
1734
return repository.StreamSource.get_stream(source, search)
1735
return source.get_stream(search)
1737
def _get_stream(self, repo, search):
1738
"""Core worker to get a stream from repo for search.
1740
This is used by both get_stream and the stacking support logic. It
1741
deliberately gets a stream for repo which does not need to be
1742
self.from_repository. In the event that repo is not Remote, or
1743
cannot do a smart stream, a fallback is made to the generic
1744
repository._get_stream() interface, via self._real_stream.
1746
In the event of stacking, streams from _get_stream will not
1747
contain all the data for search - this is normal (see get_stream).
1749
:param repo: A repository.
1750
:param search: A search.
1752
# Fallbacks may be non-smart
1753
if not isinstance(repo, RemoteRepository):
1754
return self._real_stream(repo, search)
1755
client = repo._client
1756
medium = client._medium
1757
if medium._is_remote_before((1, 13)):
1758
# streaming was added in 1.13
1759
return self._real_stream(repo, search)
1760
path = repo.bzrdir._path_for_remote_call(client)
1762
search_bytes = repo._serialise_search_result(search)
1763
response = repo._call_with_body_bytes_expecting_body(
1764
'Repository.get_stream',
1765
(path, self.to_format.network_name()), search_bytes)
1766
response_tuple, response_handler = response
1767
except errors.UnknownSmartMethod:
1768
medium._remember_remote_is_before((1,13))
1769
return self._real_stream(repo, search)
1770
if response_tuple[0] != 'ok':
1771
raise errors.UnexpectedSmartServerResponse(response_tuple)
1772
byte_stream = response_handler.read_streamed_body()
1773
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1774
if src_format.network_name() != repo._format.network_name():
1775
raise AssertionError(
1776
"Mismatched RemoteRepository and stream src %r, %r" % (
1777
src_format.network_name(), repo._format.network_name()))
1780
def missing_parents_chain(self, search, sources):
1781
"""Chain multiple streams together to handle stacking.
1783
:param search: The overall search to satisfy with streams.
1784
:param sources: A list of Repository objects to query.
1786
self.serialiser = self.to_format._serializer
1787
self.seen_revs = set()
1788
self.referenced_revs = set()
1789
# If there are heads in the search, or the key count is > 0, we are not
1791
while not search.is_empty() and len(sources) > 1:
1792
source = sources.pop(0)
1793
stream = self._get_stream(source, search)
1794
for kind, substream in stream:
1795
if kind != 'revisions':
1796
yield kind, substream
1798
yield kind, self.missing_parents_rev_handler(substream)
1799
search = search.refine(self.seen_revs, self.referenced_revs)
1800
self.seen_revs = set()
1801
self.referenced_revs = set()
1802
if not search.is_empty():
1803
for kind, stream in self._get_stream(sources[0], search):
1806
def missing_parents_rev_handler(self, substream):
1807
for content in substream:
1808
revision_bytes = content.get_bytes_as('fulltext')
1809
revision = self.serialiser.read_revision_from_string(revision_bytes)
1810
self.seen_revs.add(content.key[-1])
1811
self.referenced_revs.update(revision.parent_ids)
1815
class RemoteBranchLockableFiles(LockableFiles):
1816
"""A 'LockableFiles' implementation that talks to a smart server.
1818
This is not a public interface class.
1821
def __init__(self, bzrdir, _client):
1822
self.bzrdir = bzrdir
1823
self._client = _client
1824
self._need_find_modes = True
1825
LockableFiles.__init__(
1826
self, bzrdir.get_branch_transport(None),
1827
'lock', lockdir.LockDir)
1829
def _find_modes(self):
1830
# RemoteBranches don't let the client set the mode of control files.
1831
self._dir_mode = None
1832
self._file_mode = None
1835
class RemoteBranchFormat(branch.BranchFormat):
1837
def __init__(self, network_name=None):
1838
super(RemoteBranchFormat, self).__init__()
1839
self._matchingbzrdir = RemoteBzrDirFormat()
1840
self._matchingbzrdir.set_branch_format(self)
1841
self._custom_format = None
1842
self._network_name = network_name
1844
def __eq__(self, other):
1845
return (isinstance(other, RemoteBranchFormat) and
1846
self.__dict__ == other.__dict__)
1848
def _ensure_real(self):
1849
if self._custom_format is None:
1850
self._custom_format = branch.network_format_registry.get(
1853
def get_format_description(self):
1854
return 'Remote BZR Branch'
1856
def network_name(self):
1857
return self._network_name
1859
def open(self, a_bzrdir, ignore_fallbacks=False):
1860
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
1862
def _vfs_initialize(self, a_bzrdir):
1863
# Initialisation when using a local bzrdir object, or a non-vfs init
1864
# method is not available on the server.
1865
# self._custom_format is always set - the start of initialize ensures
1867
if isinstance(a_bzrdir, RemoteBzrDir):
1868
a_bzrdir._ensure_real()
1869
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1871
# We assume the bzrdir is parameterised; it may not be.
1872
result = self._custom_format.initialize(a_bzrdir)
1873
if (isinstance(a_bzrdir, RemoteBzrDir) and
1874
not isinstance(result, RemoteBranch)):
1875
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1878
def initialize(self, a_bzrdir):
1879
# 1) get the network name to use.
1880
if self._custom_format:
1881
network_name = self._custom_format.network_name()
1883
# Select the current bzrlib default and ask for that.
1884
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1885
reference_format = reference_bzrdir_format.get_branch_format()
1886
self._custom_format = reference_format
1887
network_name = reference_format.network_name()
1888
# Being asked to create on a non RemoteBzrDir:
1889
if not isinstance(a_bzrdir, RemoteBzrDir):
1890
return self._vfs_initialize(a_bzrdir)
1891
medium = a_bzrdir._client._medium
1892
if medium._is_remote_before((1, 13)):
1893
return self._vfs_initialize(a_bzrdir)
1894
# Creating on a remote bzr dir.
1895
# 2) try direct creation via RPC
1896
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1897
verb = 'BzrDir.create_branch'
1899
response = a_bzrdir._call(verb, path, network_name)
1900
except errors.UnknownSmartMethod:
1901
# Fallback - use vfs methods
1902
medium._remember_remote_is_before((1, 13))
1903
return self._vfs_initialize(a_bzrdir)
1904
if response[0] != 'ok':
1905
raise errors.UnexpectedSmartServerResponse(response)
1906
# Turn the response into a RemoteRepository object.
1907
format = RemoteBranchFormat(network_name=response[1])
1908
repo_format = response_tuple_to_repo_format(response[3:])
1909
if response[2] == '':
1910
repo_bzrdir = a_bzrdir
1912
repo_bzrdir = RemoteBzrDir(
1913
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1915
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1916
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1917
format=format, setup_stacking=False)
1918
# XXX: We know this is a new branch, so it must have revno 0, revid
1919
# NULL_REVISION. Creating the branch locked would make this be unable
1920
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1921
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1922
return remote_branch
1924
def make_tags(self, branch):
1926
return self._custom_format.make_tags(branch)
1928
def supports_tags(self):
1929
# Remote branches might support tags, but we won't know until we
1930
# access the real remote branch.
1932
return self._custom_format.supports_tags()
1934
def supports_stacking(self):
1936
return self._custom_format.supports_stacking()
1938
def supports_set_append_revisions_only(self):
1940
return self._custom_format.supports_set_append_revisions_only()
1943
class RemoteBranch(branch.Branch, _RpcHelper):
1944
"""Branch stored on a server accessed by HPSS RPC.
1946
At the moment most operations are mapped down to simple file operations.
1949
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1950
_client=None, format=None, setup_stacking=True):
1951
"""Create a RemoteBranch instance.
1953
:param real_branch: An optional local implementation of the branch
1954
format, usually accessing the data via the VFS.
1955
:param _client: Private parameter for testing.
1956
:param format: A RemoteBranchFormat object, None to create one
1957
automatically. If supplied it should have a network_name already
1959
:param setup_stacking: If True make an RPC call to determine the
1960
stacked (or not) status of the branch. If False assume the branch
1963
# We intentionally don't call the parent class's __init__, because it
1964
# will try to assign to self.tags, which is a property in this subclass.
1965
# And the parent's __init__ doesn't do much anyway.
1966
self.bzrdir = remote_bzrdir
1967
if _client is not None:
1968
self._client = _client
1970
self._client = remote_bzrdir._client
1971
self.repository = remote_repository
1972
if real_branch is not None:
1973
self._real_branch = real_branch
1974
# Give the remote repository the matching real repo.
1975
real_repo = self._real_branch.repository
1976
if isinstance(real_repo, RemoteRepository):
1977
real_repo._ensure_real()
1978
real_repo = real_repo._real_repository
1979
self.repository._set_real_repository(real_repo)
1980
# Give the branch the remote repository to let fast-pathing happen.
1981
self._real_branch.repository = self.repository
1983
self._real_branch = None
1984
# Fill out expected attributes of branch for bzrlib API users.
1985
self._clear_cached_state()
1986
self.base = self.bzrdir.root_transport.base
1987
self._control_files = None
1988
self._lock_mode = None
1989
self._lock_token = None
1990
self._repo_lock_token = None
1991
self._lock_count = 0
1992
self._leave_lock = False
1993
# Setup a format: note that we cannot call _ensure_real until all the
1994
# attributes above are set: This code cannot be moved higher up in this
1997
self._format = RemoteBranchFormat()
1998
if real_branch is not None:
1999
self._format._network_name = \
2000
self._real_branch._format.network_name()
2002
self._format = format
2003
if not self._format._network_name:
2004
# Did not get from open_branchV2 - old server.
2006
self._format._network_name = \
2007
self._real_branch._format.network_name()
2008
self.tags = self._format.make_tags(self)
2009
# The base class init is not called, so we duplicate this:
2010
hooks = branch.Branch.hooks['open']
2013
self._is_stacked = False
2015
self._setup_stacking()
2017
def _setup_stacking(self):
2018
# configure stacking into the remote repository, by reading it from
2021
fallback_url = self.get_stacked_on_url()
2022
except (errors.NotStacked, errors.UnstackableBranchFormat,
2023
errors.UnstackableRepositoryFormat), e:
2025
self._is_stacked = True
2026
self._activate_fallback_location(fallback_url)
2028
def _get_config(self):
2029
return RemoteBranchConfig(self)
2031
def _get_real_transport(self):
2032
# if we try vfs access, return the real branch's vfs transport
2034
return self._real_branch._transport
2036
_transport = property(_get_real_transport)
2039
return "%s(%s)" % (self.__class__.__name__, self.base)
2043
def _ensure_real(self):
2044
"""Ensure that there is a _real_branch set.
2046
Used before calls to self._real_branch.
2048
if self._real_branch is None:
2049
if not vfs.vfs_enabled():
2050
raise AssertionError('smart server vfs must be enabled '
2051
'to use vfs implementation')
2052
self.bzrdir._ensure_real()
2053
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
2054
if self.repository._real_repository is None:
2055
# Give the remote repository the matching real repo.
2056
real_repo = self._real_branch.repository
2057
if isinstance(real_repo, RemoteRepository):
2058
real_repo._ensure_real()
2059
real_repo = real_repo._real_repository
2060
self.repository._set_real_repository(real_repo)
2061
# Give the real branch the remote repository to let fast-pathing
2063
self._real_branch.repository = self.repository
2064
if self._lock_mode == 'r':
2065
self._real_branch.lock_read()
2066
elif self._lock_mode == 'w':
2067
self._real_branch.lock_write(token=self._lock_token)
2069
def _translate_error(self, err, **context):
2070
self.repository._translate_error(err, branch=self, **context)
2072
def _clear_cached_state(self):
2073
super(RemoteBranch, self)._clear_cached_state()
2074
if self._real_branch is not None:
2075
self._real_branch._clear_cached_state()
2077
def _clear_cached_state_of_remote_branch_only(self):
2078
"""Like _clear_cached_state, but doesn't clear the cache of
2081
This is useful when falling back to calling a method of
2082
self._real_branch that changes state. In that case the underlying
2083
branch changes, so we need to invalidate this RemoteBranch's cache of
2084
it. However, there's no need to invalidate the _real_branch's cache
2085
too, in fact doing so might harm performance.
2087
super(RemoteBranch, self)._clear_cached_state()
2090
def control_files(self):
2091
# Defer actually creating RemoteBranchLockableFiles until its needed,
2092
# because it triggers an _ensure_real that we otherwise might not need.
2093
if self._control_files is None:
2094
self._control_files = RemoteBranchLockableFiles(
2095
self.bzrdir, self._client)
2096
return self._control_files
2098
def _get_checkout_format(self):
2100
return self._real_branch._get_checkout_format()
2102
def get_physical_lock_status(self):
2103
"""See Branch.get_physical_lock_status()."""
2104
# should be an API call to the server, as branches must be lockable.
2106
return self._real_branch.get_physical_lock_status()
2108
def get_stacked_on_url(self):
2109
"""Get the URL this branch is stacked against.
2111
:raises NotStacked: If the branch is not stacked.
2112
:raises UnstackableBranchFormat: If the branch does not support
2114
:raises UnstackableRepositoryFormat: If the repository does not support
2118
# there may not be a repository yet, so we can't use
2119
# self._translate_error, so we can't use self._call either.
2120
response = self._client.call('Branch.get_stacked_on_url',
2121
self._remote_path())
2122
except errors.ErrorFromSmartServer, err:
2123
# there may not be a repository yet, so we can't call through
2124
# its _translate_error
2125
_translate_error(err, branch=self)
2126
except errors.UnknownSmartMethod, err:
2128
return self._real_branch.get_stacked_on_url()
2129
if response[0] != 'ok':
2130
raise errors.UnexpectedSmartServerResponse(response)
2133
def set_stacked_on_url(self, url):
2134
branch.Branch.set_stacked_on_url(self, url)
2136
self._is_stacked = False
2138
self._is_stacked = True
2140
def _vfs_get_tags_bytes(self):
2142
return self._real_branch._get_tags_bytes()
2144
def _get_tags_bytes(self):
2145
medium = self._client._medium
2146
if medium._is_remote_before((1, 13)):
2147
return self._vfs_get_tags_bytes()
2149
response = self._call('Branch.get_tags_bytes', self._remote_path())
2150
except errors.UnknownSmartMethod:
2151
medium._remember_remote_is_before((1, 13))
2152
return self._vfs_get_tags_bytes()
2155
def _vfs_set_tags_bytes(self):
2157
return self._real_branch._set_tags_bytes()
2159
def _set_tags_bytes(self, bytes):
2160
medium = self._client._medium
2161
if medium._is_remote_before((1, 18)):
2162
self._vfs_set_tags_bytes(bytes)
2165
self._remote_path(), self._lock_token, self._repo_lock_token)
2166
response = self._call_with_body_bytes(
2167
'Branch.set_tags_bytes', args, bytes)
2168
except errors.UnknownSmartMethod:
2169
medium._remember_remote_is_before((1, 18))
2170
self._vfs_set_tags_bytes(bytes)
2172
def lock_read(self):
2173
self.repository.lock_read()
2174
if not self._lock_mode:
2175
self._lock_mode = 'r'
2176
self._lock_count = 1
2177
if self._real_branch is not None:
2178
self._real_branch.lock_read()
2180
self._lock_count += 1
2182
def _remote_lock_write(self, token):
2184
branch_token = repo_token = ''
2186
branch_token = token
2187
repo_token = self.repository.lock_write()
2188
self.repository.unlock()
2189
err_context = {'token': token}
2190
response = self._call(
2191
'Branch.lock_write', self._remote_path(), branch_token,
2192
repo_token or '', **err_context)
2193
if response[0] != 'ok':
2194
raise errors.UnexpectedSmartServerResponse(response)
2195
ok, branch_token, repo_token = response
2196
return branch_token, repo_token
2198
def lock_write(self, token=None):
2199
if not self._lock_mode:
2200
# Lock the branch and repo in one remote call.
2201
remote_tokens = self._remote_lock_write(token)
2202
self._lock_token, self._repo_lock_token = remote_tokens
2203
if not self._lock_token:
2204
raise SmartProtocolError('Remote server did not return a token!')
2205
# Tell the self.repository object that it is locked.
2206
self.repository.lock_write(
2207
self._repo_lock_token, _skip_rpc=True)
2209
if self._real_branch is not None:
2210
self._real_branch.lock_write(token=self._lock_token)
2211
if token is not None:
2212
self._leave_lock = True
2214
self._leave_lock = False
2215
self._lock_mode = 'w'
2216
self._lock_count = 1
2217
elif self._lock_mode == 'r':
2218
raise errors.ReadOnlyTransaction
2220
if token is not None:
2221
# A token was given to lock_write, and we're relocking, so
2222
# check that the given token actually matches the one we
2224
if token != self._lock_token:
2225
raise errors.TokenMismatch(token, self._lock_token)
2226
self._lock_count += 1
2227
# Re-lock the repository too.
2228
self.repository.lock_write(self._repo_lock_token)
2229
return self._lock_token or None
2231
def _unlock(self, branch_token, repo_token):
2232
err_context = {'token': str((branch_token, repo_token))}
2233
response = self._call(
2234
'Branch.unlock', self._remote_path(), branch_token,
2235
repo_token or '', **err_context)
2236
if response == ('ok',):
2238
raise errors.UnexpectedSmartServerResponse(response)
2242
self._lock_count -= 1
2243
if not self._lock_count:
2244
self._clear_cached_state()
2245
mode = self._lock_mode
2246
self._lock_mode = None
2247
if self._real_branch is not None:
2248
if (not self._leave_lock and mode == 'w' and
2249
self._repo_lock_token):
2250
# If this RemoteBranch will remove the physical lock
2251
# for the repository, make sure the _real_branch
2252
# doesn't do it first. (Because the _real_branch's
2253
# repository is set to be the RemoteRepository.)
2254
self._real_branch.repository.leave_lock_in_place()
2255
self._real_branch.unlock()
2257
# Only write-locked branched need to make a remote method
2258
# call to perform the unlock.
2260
if not self._lock_token:
2261
raise AssertionError('Locked, but no token!')
2262
branch_token = self._lock_token
2263
repo_token = self._repo_lock_token
2264
self._lock_token = None
2265
self._repo_lock_token = None
2266
if not self._leave_lock:
2267
self._unlock(branch_token, repo_token)
2269
self.repository.unlock()
2271
def break_lock(self):
2273
return self._real_branch.break_lock()
2275
def leave_lock_in_place(self):
2276
if not self._lock_token:
2277
raise NotImplementedError(self.leave_lock_in_place)
2278
self._leave_lock = True
2280
def dont_leave_lock_in_place(self):
2281
if not self._lock_token:
2282
raise NotImplementedError(self.dont_leave_lock_in_place)
2283
self._leave_lock = False
2285
def get_rev_id(self, revno, history=None):
2287
return _mod_revision.NULL_REVISION
2288
last_revision_info = self.last_revision_info()
2289
ok, result = self.repository.get_rev_id_for_revno(
2290
revno, last_revision_info)
2293
missing_parent = result[1]
2294
# Either the revision named by the server is missing, or its parent
2295
# is. Call get_parent_map to determine which, so that we report a
2297
parent_map = self.repository.get_parent_map([missing_parent])
2298
if missing_parent in parent_map:
2299
missing_parent = parent_map[missing_parent]
2300
raise errors.RevisionNotPresent(missing_parent, self.repository)
2302
def _last_revision_info(self):
2303
response = self._call('Branch.last_revision_info', self._remote_path())
2304
if response[0] != 'ok':
2305
raise SmartProtocolError('unexpected response code %s' % (response,))
2306
revno = int(response[1])
2307
last_revision = response[2]
2308
return (revno, last_revision)
2310
def _gen_revision_history(self):
2311
"""See Branch._gen_revision_history()."""
2312
if self._is_stacked:
2314
return self._real_branch._gen_revision_history()
2315
response_tuple, response_handler = self._call_expecting_body(
2316
'Branch.revision_history', self._remote_path())
2317
if response_tuple[0] != 'ok':
2318
raise errors.UnexpectedSmartServerResponse(response_tuple)
2319
result = response_handler.read_body_bytes().split('\x00')
2324
def _remote_path(self):
2325
return self.bzrdir._path_for_remote_call(self._client)
2327
def _set_last_revision_descendant(self, revision_id, other_branch,
2328
allow_diverged=False, allow_overwrite_descendant=False):
2329
# This performs additional work to meet the hook contract; while its
2330
# undesirable, we have to synthesise the revno to call the hook, and
2331
# not calling the hook is worse as it means changes can't be prevented.
2332
# Having calculated this though, we can't just call into
2333
# set_last_revision_info as a simple call, because there is a set_rh
2334
# hook that some folk may still be using.
2335
old_revno, old_revid = self.last_revision_info()
2336
history = self._lefthand_history(revision_id)
2337
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2338
err_context = {'other_branch': other_branch}
2339
response = self._call('Branch.set_last_revision_ex',
2340
self._remote_path(), self._lock_token, self._repo_lock_token,
2341
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2343
self._clear_cached_state()
2344
if len(response) != 3 and response[0] != 'ok':
2345
raise errors.UnexpectedSmartServerResponse(response)
2346
new_revno, new_revision_id = response[1:]
2347
self._last_revision_info_cache = new_revno, new_revision_id
2348
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2349
if self._real_branch is not None:
2350
cache = new_revno, new_revision_id
2351
self._real_branch._last_revision_info_cache = cache
2353
def _set_last_revision(self, revision_id):
2354
old_revno, old_revid = self.last_revision_info()
2355
# This performs additional work to meet the hook contract; while its
2356
# undesirable, we have to synthesise the revno to call the hook, and
2357
# not calling the hook is worse as it means changes can't be prevented.
2358
# Having calculated this though, we can't just call into
2359
# set_last_revision_info as a simple call, because there is a set_rh
2360
# hook that some folk may still be using.
2361
history = self._lefthand_history(revision_id)
2362
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2363
self._clear_cached_state()
2364
response = self._call('Branch.set_last_revision',
2365
self._remote_path(), self._lock_token, self._repo_lock_token,
2367
if response != ('ok',):
2368
raise errors.UnexpectedSmartServerResponse(response)
2369
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2372
def set_revision_history(self, rev_history):
2373
# Send just the tip revision of the history; the server will generate
2374
# the full history from that. If the revision doesn't exist in this
2375
# branch, NoSuchRevision will be raised.
2376
if rev_history == []:
2379
rev_id = rev_history[-1]
2380
self._set_last_revision(rev_id)
2381
for hook in branch.Branch.hooks['set_rh']:
2382
hook(self, rev_history)
2383
self._cache_revision_history(rev_history)
2385
def _get_parent_location(self):
2386
medium = self._client._medium
2387
if medium._is_remote_before((1, 13)):
2388
return self._vfs_get_parent_location()
2390
response = self._call('Branch.get_parent', self._remote_path())
2391
except errors.UnknownSmartMethod:
2392
medium._remember_remote_is_before((1, 13))
2393
return self._vfs_get_parent_location()
2394
if len(response) != 1:
2395
raise errors.UnexpectedSmartServerResponse(response)
2396
parent_location = response[0]
2397
if parent_location == '':
2399
return parent_location
2401
def _vfs_get_parent_location(self):
2403
return self._real_branch._get_parent_location()
2405
def _set_parent_location(self, url):
2406
medium = self._client._medium
2407
if medium._is_remote_before((1, 15)):
2408
return self._vfs_set_parent_location(url)
2410
call_url = url or ''
2411
if type(call_url) is not str:
2412
raise AssertionError('url must be a str or None (%s)' % url)
2413
response = self._call('Branch.set_parent_location',
2414
self._remote_path(), self._lock_token, self._repo_lock_token,
2416
except errors.UnknownSmartMethod:
2417
medium._remember_remote_is_before((1, 15))
2418
return self._vfs_set_parent_location(url)
2420
raise errors.UnexpectedSmartServerResponse(response)
2422
def _vfs_set_parent_location(self, url):
2424
return self._real_branch._set_parent_location(url)
2427
def pull(self, source, overwrite=False, stop_revision=None,
2429
self._clear_cached_state_of_remote_branch_only()
2431
return self._real_branch.pull(
2432
source, overwrite=overwrite, stop_revision=stop_revision,
2433
_override_hook_target=self, **kwargs)
2436
def push(self, target, overwrite=False, stop_revision=None):
2438
return self._real_branch.push(
2439
target, overwrite=overwrite, stop_revision=stop_revision,
2440
_override_hook_source_branch=self)
2442
def is_locked(self):
2443
return self._lock_count >= 1
2446
def revision_id_to_revno(self, revision_id):
2448
return self._real_branch.revision_id_to_revno(revision_id)
2451
def set_last_revision_info(self, revno, revision_id):
2452
# XXX: These should be returned by the set_last_revision_info verb
2453
old_revno, old_revid = self.last_revision_info()
2454
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2455
revision_id = ensure_null(revision_id)
2457
response = self._call('Branch.set_last_revision_info',
2458
self._remote_path(), self._lock_token, self._repo_lock_token,
2459
str(revno), revision_id)
2460
except errors.UnknownSmartMethod:
2462
self._clear_cached_state_of_remote_branch_only()
2463
self._real_branch.set_last_revision_info(revno, revision_id)
2464
self._last_revision_info_cache = revno, revision_id
2466
if response == ('ok',):
2467
self._clear_cached_state()
2468
self._last_revision_info_cache = revno, revision_id
2469
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2470
# Update the _real_branch's cache too.
2471
if self._real_branch is not None:
2472
cache = self._last_revision_info_cache
2473
self._real_branch._last_revision_info_cache = cache
2475
raise errors.UnexpectedSmartServerResponse(response)
2478
def generate_revision_history(self, revision_id, last_rev=None,
2480
medium = self._client._medium
2481
if not medium._is_remote_before((1, 6)):
2482
# Use a smart method for 1.6 and above servers
2484
self._set_last_revision_descendant(revision_id, other_branch,
2485
allow_diverged=True, allow_overwrite_descendant=True)
2487
except errors.UnknownSmartMethod:
2488
medium._remember_remote_is_before((1, 6))
2489
self._clear_cached_state_of_remote_branch_only()
2490
self.set_revision_history(self._lefthand_history(revision_id,
2491
last_rev=last_rev,other_branch=other_branch))
2493
def set_push_location(self, location):
2495
return self._real_branch.set_push_location(location)
2498
class RemoteConfig(object):
2499
"""A Config that reads and writes from smart verbs.
2501
It is a low-level object that considers config data to be name/value pairs
2502
that may be associated with a section. Assigning meaning to the these
2503
values is done at higher levels like bzrlib.config.TreeConfig.
2506
def get_option(self, name, section=None, default=None):
2507
"""Return the value associated with a named option.
2509
:param name: The name of the value
2510
:param section: The section the option is in (if any)
2511
:param default: The value to return if the value is not set
2512
:return: The value or default value
2515
configobj = self._get_configobj()
2517
section_obj = configobj
2520
section_obj = configobj[section]
2523
return section_obj.get(name, default)
2524
except errors.UnknownSmartMethod:
2525
return self._vfs_get_option(name, section, default)
2527
def _response_to_configobj(self, response):
2528
if len(response[0]) and response[0][0] != 'ok':
2529
raise errors.UnexpectedSmartServerResponse(response)
2530
lines = response[1].read_body_bytes().splitlines()
2531
return config.ConfigObj(lines, encoding='utf-8')
2534
class RemoteBranchConfig(RemoteConfig):
2535
"""A RemoteConfig for Branches."""
2537
def __init__(self, branch):
2538
self._branch = branch
2540
def _get_configobj(self):
2541
path = self._branch._remote_path()
2542
response = self._branch._client.call_expecting_body(
2543
'Branch.get_config_file', path)
2544
return self._response_to_configobj(response)
2546
def set_option(self, value, name, section=None):
2547
"""Set the value associated with a named option.
2549
:param value: The value to set
2550
:param name: The name of the value to set
2551
:param section: The section the option is in (if any)
2553
medium = self._branch._client._medium
2554
if medium._is_remote_before((1, 14)):
2555
return self._vfs_set_option(value, name, section)
2557
path = self._branch._remote_path()
2558
response = self._branch._client.call('Branch.set_config_option',
2559
path, self._branch._lock_token, self._branch._repo_lock_token,
2560
value.encode('utf8'), name, section or '')
2561
except errors.UnknownSmartMethod:
2562
medium._remember_remote_is_before((1, 14))
2563
return self._vfs_set_option(value, name, section)
2565
raise errors.UnexpectedSmartServerResponse(response)
2567
def _real_object(self):
2568
self._branch._ensure_real()
2569
return self._branch._real_branch
2571
def _vfs_set_option(self, value, name, section=None):
2572
return self._real_object()._get_config().set_option(
2573
value, name, section)
2576
class RemoteBzrDirConfig(RemoteConfig):
2577
"""A RemoteConfig for BzrDirs."""
2579
def __init__(self, bzrdir):
2580
self._bzrdir = bzrdir
2582
def _get_configobj(self):
2583
medium = self._bzrdir._client._medium
2584
verb = 'BzrDir.get_config_file'
2585
if medium._is_remote_before((1, 15)):
2586
raise errors.UnknownSmartMethod(verb)
2587
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2588
response = self._bzrdir._call_expecting_body(
2590
return self._response_to_configobj(response)
2592
def _vfs_get_option(self, name, section, default):
2593
return self._real_object()._get_config().get_option(
2594
name, section, default)
2596
def set_option(self, value, name, section=None):
2597
"""Set the value associated with a named option.
2599
:param value: The value to set
2600
:param name: The name of the value to set
2601
:param section: The section the option is in (if any)
2603
return self._real_object()._get_config().set_option(
2604
value, name, section)
2606
def _real_object(self):
2607
self._bzrdir._ensure_real()
2608
return self._bzrdir._real_bzrdir
2612
def _extract_tar(tar, to_dir):
2613
"""Extract all the contents of a tarfile object.
2615
A replacement for extractall, which is not present in python2.4
2618
tar.extract(tarinfo, to_dir)
2621
def _translate_error(err, **context):
2622
"""Translate an ErrorFromSmartServer into a more useful error.
2624
Possible context keys:
2632
If the error from the server doesn't match a known pattern, then
2633
UnknownErrorFromSmartServer is raised.
2637
return context[name]
2638
except KeyError, key_err:
2639
mutter('Missing key %r in context %r', key_err.args[0], context)
2642
"""Get the path from the context if present, otherwise use first error
2646
return context['path']
2647
except KeyError, key_err:
2649
return err.error_args[0]
2650
except IndexError, idx_err:
2652
'Missing key %r in context %r', key_err.args[0], context)
2655
if err.error_verb == 'NoSuchRevision':
2656
raise NoSuchRevision(find('branch'), err.error_args[0])
2657
elif err.error_verb == 'nosuchrevision':
2658
raise NoSuchRevision(find('repository'), err.error_args[0])
2659
elif err.error_tuple == ('nobranch',):
2660
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2661
elif err.error_verb == 'norepository':
2662
raise errors.NoRepositoryPresent(find('bzrdir'))
2663
elif err.error_verb == 'LockContention':
2664
raise errors.LockContention('(remote lock)')
2665
elif err.error_verb == 'UnlockableTransport':
2666
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2667
elif err.error_verb == 'LockFailed':
2668
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2669
elif err.error_verb == 'TokenMismatch':
2670
raise errors.TokenMismatch(find('token'), '(remote token)')
2671
elif err.error_verb == 'Diverged':
2672
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2673
elif err.error_verb == 'TipChangeRejected':
2674
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2675
elif err.error_verb == 'UnstackableBranchFormat':
2676
raise errors.UnstackableBranchFormat(*err.error_args)
2677
elif err.error_verb == 'UnstackableRepositoryFormat':
2678
raise errors.UnstackableRepositoryFormat(*err.error_args)
2679
elif err.error_verb == 'NotStacked':
2680
raise errors.NotStacked(branch=find('branch'))
2681
elif err.error_verb == 'PermissionDenied':
2683
if len(err.error_args) >= 2:
2684
extra = err.error_args[1]
2687
raise errors.PermissionDenied(path, extra=extra)
2688
elif err.error_verb == 'ReadError':
2690
raise errors.ReadError(path)
2691
elif err.error_verb == 'NoSuchFile':
2693
raise errors.NoSuchFile(path)
2694
elif err.error_verb == 'FileExists':
2695
raise errors.FileExists(err.error_args[0])
2696
elif err.error_verb == 'DirectoryNotEmpty':
2697
raise errors.DirectoryNotEmpty(err.error_args[0])
2698
elif err.error_verb == 'ShortReadvError':
2699
args = err.error_args
2700
raise errors.ShortReadvError(
2701
args[0], int(args[1]), int(args[2]), int(args[3]))
2702
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2703
encoding = str(err.error_args[0]) # encoding must always be a string
2704
val = err.error_args[1]
2705
start = int(err.error_args[2])
2706
end = int(err.error_args[3])
2707
reason = str(err.error_args[4]) # reason must always be a string
2708
if val.startswith('u:'):
2709
val = val[2:].decode('utf-8')
2710
elif val.startswith('s:'):
2711
val = val[2:].decode('base64')
2712
if err.error_verb == 'UnicodeDecodeError':
2713
raise UnicodeDecodeError(encoding, val, start, end, reason)
2714
elif err.error_verb == 'UnicodeEncodeError':
2715
raise UnicodeEncodeError(encoding, val, start, end, reason)
2716
elif err.error_verb == 'ReadOnlyError':
2717
raise errors.TransportNotPossible('readonly transport')
2718
raise errors.UnknownErrorFromSmartServer(err)