1
# Copyright (C) 2006, 2007, 2008, 2009 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
31
revision as _mod_revision,
34
from bzrlib.branch import BranchReferenceFormat
35
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
36
from bzrlib.decorators import needs_read_lock, needs_write_lock
37
from bzrlib.errors import (
41
from bzrlib.lockable_files import LockableFiles
42
from bzrlib.smart import client, vfs, repository as smart_repo
43
from bzrlib.revision import ensure_null, NULL_REVISION
44
from bzrlib.trace import mutter, note, warning
47
class _RpcHelper(object):
48
"""Mixin class that helps with issuing RPCs."""
50
def _call(self, method, *args, **err_context):
52
return self._client.call(method, *args)
53
except errors.ErrorFromSmartServer, err:
54
self._translate_error(err, **err_context)
56
def _call_expecting_body(self, method, *args, **err_context):
58
return self._client.call_expecting_body(method, *args)
59
except errors.ErrorFromSmartServer, err:
60
self._translate_error(err, **err_context)
62
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
65
return self._client.call_with_body_bytes_expecting_body(
66
method, args, body_bytes)
67
except errors.ErrorFromSmartServer, err:
68
self._translate_error(err, **err_context)
71
def response_tuple_to_repo_format(response):
72
"""Convert a response tuple describing a repository format to a format."""
73
format = RemoteRepositoryFormat()
74
format._rich_root_data = (response[0] == 'yes')
75
format._supports_tree_reference = (response[1] == 'yes')
76
format._supports_external_lookups = (response[2] == 'yes')
77
format._network_name = response[3]
81
# Note: RemoteBzrDirFormat is in bzrdir.py
83
class RemoteBzrDir(BzrDir, _RpcHelper):
84
"""Control directory on a remote server, accessed via bzr:// or similar."""
86
def __init__(self, transport, format, _client=None):
87
"""Construct a RemoteBzrDir.
89
:param _client: Private parameter for testing. Disables probing and the
92
BzrDir.__init__(self, transport, format)
93
# this object holds a delegated bzrdir that uses file-level operations
94
# to talk to the other side
95
self._real_bzrdir = None
96
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
97
# create_branch for details.
98
self._next_open_branch_result = None
101
medium = transport.get_smart_medium()
102
self._client = client._SmartClient(medium)
104
self._client = _client
107
path = self._path_for_remote_call(self._client)
108
response = self._call('BzrDir.open', path)
109
if response not in [('yes',), ('no',)]:
110
raise errors.UnexpectedSmartServerResponse(response)
111
if response == ('no',):
112
raise errors.NotBranchError(path=transport.base)
114
def _ensure_real(self):
115
"""Ensure that there is a _real_bzrdir set.
117
Used before calls to self._real_bzrdir.
119
if not self._real_bzrdir:
120
self._real_bzrdir = BzrDir.open_from_transport(
121
self.root_transport, _server_formats=False)
122
self._format._network_name = \
123
self._real_bzrdir._format.network_name()
125
def _translate_error(self, err, **context):
126
_translate_error(err, bzrdir=self, **context)
128
def break_lock(self):
129
# Prevent aliasing problems in the next_open_branch_result cache.
130
# See create_branch for rationale.
131
self._next_open_branch_result = None
132
return BzrDir.break_lock(self)
134
def _vfs_cloning_metadir(self, require_stacking=False):
136
return self._real_bzrdir.cloning_metadir(
137
require_stacking=require_stacking)
139
def cloning_metadir(self, require_stacking=False):
140
medium = self._client._medium
141
if medium._is_remote_before((1, 13)):
142
return self._vfs_cloning_metadir(require_stacking=require_stacking)
143
verb = 'BzrDir.cloning_metadir'
148
path = self._path_for_remote_call(self._client)
150
response = self._call(verb, path, stacking)
151
except errors.UnknownSmartMethod:
152
medium._remember_remote_is_before((1, 13))
153
return self._vfs_cloning_metadir(require_stacking=require_stacking)
154
except errors.UnknownErrorFromSmartServer, err:
155
if err.error_tuple != ('BranchReference',):
157
# We need to resolve the branch reference to determine the
158
# cloning_metadir. This causes unnecessary RPCs to open the
159
# referenced branch (and bzrdir, etc) but only when the caller
160
# didn't already resolve the branch reference.
161
referenced_branch = self.open_branch()
162
return referenced_branch.bzrdir.cloning_metadir()
163
if len(response) != 3:
164
raise errors.UnexpectedSmartServerResponse(response)
165
control_name, repo_name, branch_info = response
166
if len(branch_info) != 2:
167
raise errors.UnexpectedSmartServerResponse(response)
168
branch_ref, branch_name = branch_info
169
format = bzrdir.network_format_registry.get(control_name)
171
format.repository_format = repository.network_format_registry.get(
173
if branch_ref == 'ref':
174
# XXX: we need possible_transports here to avoid reopening the
175
# connection to the referenced location
176
ref_bzrdir = BzrDir.open(branch_name)
177
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
178
format.set_branch_format(branch_format)
179
elif branch_ref == 'branch':
181
format.set_branch_format(
182
branch.network_format_registry.get(branch_name))
184
raise errors.UnexpectedSmartServerResponse(response)
187
def create_repository(self, shared=False):
188
# as per meta1 formats - just delegate to the format object which may
190
result = self._format.repository_format.initialize(self, shared)
191
if not isinstance(result, RemoteRepository):
192
return self.open_repository()
196
def destroy_repository(self):
197
"""See BzrDir.destroy_repository"""
199
self._real_bzrdir.destroy_repository()
201
def create_branch(self):
202
# as per meta1 formats - just delegate to the format object which may
204
real_branch = self._format.get_branch_format().initialize(self)
205
if not isinstance(real_branch, RemoteBranch):
206
result = RemoteBranch(self, self.find_repository(), real_branch)
209
# BzrDir.clone_on_transport() uses the result of create_branch but does
210
# not return it to its callers; we save approximately 8% of our round
211
# trips by handing the branch we created back to the first caller to
212
# open_branch rather than probing anew. Long term we need a API in
213
# bzrdir that doesn't discard result objects (like result_branch).
215
self._next_open_branch_result = result
218
def destroy_branch(self):
219
"""See BzrDir.destroy_branch"""
221
self._real_bzrdir.destroy_branch()
222
self._next_open_branch_result = None
224
def create_workingtree(self, revision_id=None, from_branch=None):
225
raise errors.NotLocalUrl(self.transport.base)
227
def find_branch_format(self):
228
"""Find the branch 'format' for this bzrdir.
230
This might be a synthetic object for e.g. RemoteBranch and SVN.
232
b = self.open_branch()
235
def get_branch_reference(self):
236
"""See BzrDir.get_branch_reference()."""
237
response = self._get_branch_reference()
238
if response[0] == 'ref':
243
def _get_branch_reference(self):
244
path = self._path_for_remote_call(self._client)
245
medium = self._client._medium
246
if not medium._is_remote_before((1, 13)):
248
response = self._call('BzrDir.open_branchV2', path)
249
if response[0] not in ('ref', 'branch'):
250
raise errors.UnexpectedSmartServerResponse(response)
252
except errors.UnknownSmartMethod:
253
medium._remember_remote_is_before((1, 13))
254
response = self._call('BzrDir.open_branch', path)
255
if response[0] != 'ok':
256
raise errors.UnexpectedSmartServerResponse(response)
257
if response[1] != '':
258
return ('ref', response[1])
260
return ('branch', '')
262
def _get_tree_branch(self):
263
"""See BzrDir._get_tree_branch()."""
264
return None, self.open_branch()
266
def open_branch(self, _unsupported=False, ignore_fallbacks=False):
268
raise NotImplementedError('unsupported flag support not implemented yet.')
269
if self._next_open_branch_result is not None:
270
# See create_branch for details.
271
result = self._next_open_branch_result
272
self._next_open_branch_result = None
274
response = self._get_branch_reference()
275
if response[0] == 'ref':
276
# a branch reference, use the existing BranchReference logic.
277
format = BranchReferenceFormat()
278
return format.open(self, _found=True, location=response[1],
279
ignore_fallbacks=ignore_fallbacks)
280
branch_format_name = response[1]
281
if not branch_format_name:
282
branch_format_name = None
283
format = RemoteBranchFormat(network_name=branch_format_name)
284
return RemoteBranch(self, self.find_repository(), format=format,
285
setup_stacking=not ignore_fallbacks)
287
def _open_repo_v1(self, path):
288
verb = 'BzrDir.find_repository'
289
response = self._call(verb, path)
290
if response[0] != 'ok':
291
raise errors.UnexpectedSmartServerResponse(response)
292
# servers that only support the v1 method don't support external
295
repo = self._real_bzrdir.open_repository()
296
response = response + ('no', repo._format.network_name())
297
return response, repo
299
def _open_repo_v2(self, path):
300
verb = 'BzrDir.find_repositoryV2'
301
response = self._call(verb, path)
302
if response[0] != 'ok':
303
raise errors.UnexpectedSmartServerResponse(response)
305
repo = self._real_bzrdir.open_repository()
306
response = response + (repo._format.network_name(),)
307
return response, repo
309
def _open_repo_v3(self, path):
310
verb = 'BzrDir.find_repositoryV3'
311
medium = self._client._medium
312
if medium._is_remote_before((1, 13)):
313
raise errors.UnknownSmartMethod(verb)
315
response = self._call(verb, path)
316
except errors.UnknownSmartMethod:
317
medium._remember_remote_is_before((1, 13))
319
if response[0] != 'ok':
320
raise errors.UnexpectedSmartServerResponse(response)
321
return response, None
323
def open_repository(self):
324
path = self._path_for_remote_call(self._client)
326
for probe in [self._open_repo_v3, self._open_repo_v2,
329
response, real_repo = probe(path)
331
except errors.UnknownSmartMethod:
334
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
335
if response[0] != 'ok':
336
raise errors.UnexpectedSmartServerResponse(response)
337
if len(response) != 6:
338
raise SmartProtocolError('incorrect response length %s' % (response,))
339
if response[1] == '':
340
# repo is at this dir.
341
format = response_tuple_to_repo_format(response[2:])
342
# Used to support creating a real format instance when needed.
343
format._creating_bzrdir = self
344
remote_repo = RemoteRepository(self, format)
345
format._creating_repo = remote_repo
346
if real_repo is not None:
347
remote_repo._set_real_repository(real_repo)
350
raise errors.NoRepositoryPresent(self)
352
def open_workingtree(self, recommend_upgrade=True):
354
if self._real_bzrdir.has_workingtree():
355
raise errors.NotLocalUrl(self.root_transport)
357
raise errors.NoWorkingTree(self.root_transport.base)
359
def _path_for_remote_call(self, client):
360
"""Return the path to be used for this bzrdir in a remote call."""
361
return client.remote_path_from_transport(self.root_transport)
363
def get_branch_transport(self, branch_format):
365
return self._real_bzrdir.get_branch_transport(branch_format)
367
def get_repository_transport(self, repository_format):
369
return self._real_bzrdir.get_repository_transport(repository_format)
371
def get_workingtree_transport(self, workingtree_format):
373
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
375
def can_convert_format(self):
376
"""Upgrading of remote bzrdirs is not supported yet."""
379
def needs_format_conversion(self, format=None):
380
"""Upgrading of remote bzrdirs is not supported yet."""
382
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
383
% 'needs_format_conversion(format=None)')
386
def clone(self, url, revision_id=None, force_new_repo=False,
387
preserve_stacking=False):
389
return self._real_bzrdir.clone(url, revision_id=revision_id,
390
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
392
def _get_config(self):
393
return RemoteBzrDirConfig(self)
396
class RemoteRepositoryFormat(repository.RepositoryFormat):
397
"""Format for repositories accessed over a _SmartClient.
399
Instances of this repository are represented by RemoteRepository
402
The RemoteRepositoryFormat is parameterized during construction
403
to reflect the capabilities of the real, remote format. Specifically
404
the attributes rich_root_data and supports_tree_reference are set
405
on a per instance basis, and are not set (and should not be) at
408
:ivar _custom_format: If set, a specific concrete repository format that
409
will be used when initializing a repository with this
410
RemoteRepositoryFormat.
411
:ivar _creating_repo: If set, the repository object that this
412
RemoteRepositoryFormat was created for: it can be called into
413
to obtain data like the network name.
416
_matchingbzrdir = RemoteBzrDirFormat()
419
repository.RepositoryFormat.__init__(self)
420
self._custom_format = None
421
self._network_name = None
422
self._creating_bzrdir = None
423
self._supports_external_lookups = None
424
self._supports_tree_reference = None
425
self._rich_root_data = None
428
def fast_deltas(self):
430
return self._custom_format.fast_deltas
433
def rich_root_data(self):
434
if self._rich_root_data is None:
436
self._rich_root_data = self._custom_format.rich_root_data
437
return self._rich_root_data
440
def supports_external_lookups(self):
441
if self._supports_external_lookups is None:
443
self._supports_external_lookups = \
444
self._custom_format.supports_external_lookups
445
return self._supports_external_lookups
448
def supports_tree_reference(self):
449
if self._supports_tree_reference is None:
451
self._supports_tree_reference = \
452
self._custom_format.supports_tree_reference
453
return self._supports_tree_reference
455
def _vfs_initialize(self, a_bzrdir, shared):
456
"""Helper for common code in initialize."""
457
if self._custom_format:
458
# Custom format requested
459
result = self._custom_format.initialize(a_bzrdir, shared=shared)
460
elif self._creating_bzrdir is not None:
461
# Use the format that the repository we were created to back
463
prior_repo = self._creating_bzrdir.open_repository()
464
prior_repo._ensure_real()
465
result = prior_repo._real_repository._format.initialize(
466
a_bzrdir, shared=shared)
468
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
469
# support remote initialization.
470
# We delegate to a real object at this point (as RemoteBzrDir
471
# delegate to the repository format which would lead to infinite
472
# recursion if we just called a_bzrdir.create_repository.
473
a_bzrdir._ensure_real()
474
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
475
if not isinstance(result, RemoteRepository):
476
return self.open(a_bzrdir)
480
def initialize(self, a_bzrdir, shared=False):
481
# Being asked to create on a non RemoteBzrDir:
482
if not isinstance(a_bzrdir, RemoteBzrDir):
483
return self._vfs_initialize(a_bzrdir, shared)
484
medium = a_bzrdir._client._medium
485
if medium._is_remote_before((1, 13)):
486
return self._vfs_initialize(a_bzrdir, shared)
487
# Creating on a remote bzr dir.
488
# 1) get the network name to use.
489
if self._custom_format:
490
network_name = self._custom_format.network_name()
491
elif self._network_name:
492
network_name = self._network_name
494
# Select the current bzrlib default and ask for that.
495
reference_bzrdir_format = bzrdir.format_registry.get('default')()
496
reference_format = reference_bzrdir_format.repository_format
497
network_name = reference_format.network_name()
498
# 2) try direct creation via RPC
499
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
500
verb = 'BzrDir.create_repository'
506
response = a_bzrdir._call(verb, path, network_name, shared_str)
507
except errors.UnknownSmartMethod:
508
# Fallback - use vfs methods
509
medium._remember_remote_is_before((1, 13))
510
return self._vfs_initialize(a_bzrdir, shared)
512
# Turn the response into a RemoteRepository object.
513
format = response_tuple_to_repo_format(response[1:])
514
# Used to support creating a real format instance when needed.
515
format._creating_bzrdir = a_bzrdir
516
remote_repo = RemoteRepository(a_bzrdir, format)
517
format._creating_repo = remote_repo
520
def open(self, a_bzrdir):
521
if not isinstance(a_bzrdir, RemoteBzrDir):
522
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
523
return a_bzrdir.open_repository()
525
def _ensure_real(self):
526
if self._custom_format is None:
527
self._custom_format = repository.network_format_registry.get(
531
def _fetch_order(self):
533
return self._custom_format._fetch_order
536
def _fetch_uses_deltas(self):
538
return self._custom_format._fetch_uses_deltas
541
def _fetch_reconcile(self):
543
return self._custom_format._fetch_reconcile
545
def get_format_description(self):
546
return 'bzr remote repository'
548
def __eq__(self, other):
549
return self.__class__ is other.__class__
551
def check_conversion_target(self, target_format):
552
if self.rich_root_data and not target_format.rich_root_data:
553
raise errors.BadConversionTarget(
554
'Does not support rich root data.', target_format)
555
if (self.supports_tree_reference and
556
not getattr(target_format, 'supports_tree_reference', False)):
557
raise errors.BadConversionTarget(
558
'Does not support nested trees', target_format)
560
def network_name(self):
561
if self._network_name:
562
return self._network_name
563
self._creating_repo._ensure_real()
564
return self._creating_repo._real_repository._format.network_name()
567
def pack_compresses(self):
569
return self._custom_format.pack_compresses
572
def _serializer(self):
574
return self._custom_format._serializer
577
class RemoteRepository(_RpcHelper):
578
"""Repository accessed over rpc.
580
For the moment most operations are performed using local transport-backed
584
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
585
"""Create a RemoteRepository instance.
587
:param remote_bzrdir: The bzrdir hosting this repository.
588
:param format: The RemoteFormat object to use.
589
:param real_repository: If not None, a local implementation of the
590
repository logic for the repository, usually accessing the data
592
:param _client: Private testing parameter - override the smart client
593
to be used by the repository.
596
self._real_repository = real_repository
598
self._real_repository = None
599
self.bzrdir = remote_bzrdir
601
self._client = remote_bzrdir._client
603
self._client = _client
604
self._format = format
605
self._lock_mode = None
606
self._lock_token = None
608
self._leave_lock = False
609
# Cache of revision parents; misses are cached during read locks, and
610
# write locks when no _real_repository has been set.
611
self._unstacked_provider = graph.CachingParentsProvider(
612
get_parent_map=self._get_parent_map_rpc)
613
self._unstacked_provider.disable_cache()
615
# These depend on the actual remote format, so force them off for
616
# maximum compatibility. XXX: In future these should depend on the
617
# remote repository instance, but this is irrelevant until we perform
618
# reconcile via an RPC call.
619
self._reconcile_does_inventory_gc = False
620
self._reconcile_fixes_text_parents = False
621
self._reconcile_backsup_inventory = False
622
self.base = self.bzrdir.transport.base
623
# Additional places to query for data.
624
self._fallback_repositories = []
627
return "%s(%s)" % (self.__class__.__name__, self.base)
631
def abort_write_group(self, suppress_errors=False):
632
"""Complete a write group on the decorated repository.
634
Smart methods perform operations in a single step so this API
635
is not really applicable except as a compatibility thunk
636
for older plugins that don't use e.g. the CommitBuilder
639
:param suppress_errors: see Repository.abort_write_group.
642
return self._real_repository.abort_write_group(
643
suppress_errors=suppress_errors)
647
"""Decorate the real repository for now.
649
In the long term a full blown network facility is needed to avoid
650
creating a real repository object locally.
653
return self._real_repository.chk_bytes
655
def commit_write_group(self):
656
"""Complete a write group on the decorated repository.
658
Smart methods perform operations in a single step so this API
659
is not really applicable except as a compatibility thunk
660
for older plugins that don't use e.g. the CommitBuilder
664
return self._real_repository.commit_write_group()
666
def resume_write_group(self, tokens):
668
return self._real_repository.resume_write_group(tokens)
670
def suspend_write_group(self):
672
return self._real_repository.suspend_write_group()
674
def get_missing_parent_inventories(self, check_for_missing_texts=True):
676
return self._real_repository.get_missing_parent_inventories(
677
check_for_missing_texts=check_for_missing_texts)
679
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
681
return self._real_repository.get_rev_id_for_revno(
684
def get_rev_id_for_revno(self, revno, known_pair):
685
"""See Repository.get_rev_id_for_revno."""
686
path = self.bzrdir._path_for_remote_call(self._client)
688
if self._client._medium._is_remote_before((1, 17)):
689
return self._get_rev_id_for_revno_vfs(revno, known_pair)
690
response = self._call(
691
'Repository.get_rev_id_for_revno', path, revno, known_pair)
692
except errors.UnknownSmartMethod:
693
self._client._medium._remember_remote_is_before((1, 17))
694
return self._get_rev_id_for_revno_vfs(revno, known_pair)
695
if response[0] == 'ok':
696
return True, response[1]
697
elif response[0] == 'history-incomplete':
698
known_pair = response[1:3]
699
for fallback in self._fallback_repositories:
700
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
705
# Not found in any fallbacks
706
return False, known_pair
708
raise errors.UnexpectedSmartServerResponse(response)
710
def _ensure_real(self):
711
"""Ensure that there is a _real_repository set.
713
Used before calls to self._real_repository.
715
Note that _ensure_real causes many roundtrips to the server which are
716
not desirable, and prevents the use of smart one-roundtrip RPC's to
717
perform complex operations (such as accessing parent data, streaming
718
revisions etc). Adding calls to _ensure_real should only be done when
719
bringing up new functionality, adding fallbacks for smart methods that
720
require a fallback path, and never to replace an existing smart method
721
invocation. If in doubt chat to the bzr network team.
723
if self._real_repository is None:
724
if 'hpssvfs' in debug.debug_flags:
726
warning('VFS Repository access triggered\n%s',
727
''.join(traceback.format_stack()))
728
self._unstacked_provider.missing_keys.clear()
729
self.bzrdir._ensure_real()
730
self._set_real_repository(
731
self.bzrdir._real_bzrdir.open_repository())
733
def _translate_error(self, err, **context):
734
self.bzrdir._translate_error(err, repository=self, **context)
736
def find_text_key_references(self):
737
"""Find the text key references within the repository.
739
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
740
revision_ids. Each altered file-ids has the exact revision_ids that
741
altered it listed explicitly.
742
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
743
to whether they were referred to by the inventory of the
744
revision_id that they contain. The inventory texts from all present
745
revision ids are assessed to generate this report.
748
return self._real_repository.find_text_key_references()
750
def _generate_text_key_index(self):
751
"""Generate a new text key index for the repository.
753
This is an expensive function that will take considerable time to run.
755
:return: A dict mapping (file_id, revision_id) tuples to a list of
756
parents, also (file_id, revision_id) tuples.
759
return self._real_repository._generate_text_key_index()
761
def _get_revision_graph(self, revision_id):
762
"""Private method for using with old (< 1.2) servers to fallback."""
763
if revision_id is None:
765
elif revision.is_null(revision_id):
768
path = self.bzrdir._path_for_remote_call(self._client)
769
response = self._call_expecting_body(
770
'Repository.get_revision_graph', path, revision_id)
771
response_tuple, response_handler = response
772
if response_tuple[0] != 'ok':
773
raise errors.UnexpectedSmartServerResponse(response_tuple)
774
coded = response_handler.read_body_bytes()
776
# no revisions in this repository!
778
lines = coded.split('\n')
781
d = tuple(line.split())
782
revision_graph[d[0]] = d[1:]
784
return revision_graph
787
"""See Repository._get_sink()."""
788
return RemoteStreamSink(self)
790
def _get_source(self, to_format):
791
"""Return a source for streaming from this repository."""
792
return RemoteStreamSource(self, to_format)
795
def has_revision(self, revision_id):
796
"""True if this repository has a copy of the revision."""
797
# Copy of bzrlib.repository.Repository.has_revision
798
return revision_id in self.has_revisions((revision_id,))
801
def has_revisions(self, revision_ids):
802
"""Probe to find out the presence of multiple revisions.
804
:param revision_ids: An iterable of revision_ids.
805
:return: A set of the revision_ids that were present.
807
# Copy of bzrlib.repository.Repository.has_revisions
808
parent_map = self.get_parent_map(revision_ids)
809
result = set(parent_map)
810
if _mod_revision.NULL_REVISION in revision_ids:
811
result.add(_mod_revision.NULL_REVISION)
814
def _has_same_fallbacks(self, other_repo):
815
"""Returns true if the repositories have the same fallbacks."""
816
# XXX: copied from Repository; it should be unified into a base class
817
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
818
my_fb = self._fallback_repositories
819
other_fb = other_repo._fallback_repositories
820
if len(my_fb) != len(other_fb):
822
for f, g in zip(my_fb, other_fb):
823
if not f.has_same_location(g):
827
def has_same_location(self, other):
828
# TODO: Move to RepositoryBase and unify with the regular Repository
829
# one; unfortunately the tests rely on slightly different behaviour at
830
# present -- mbp 20090710
831
return (self.__class__ is other.__class__ and
832
self.bzrdir.transport.base == other.bzrdir.transport.base)
834
def get_graph(self, other_repository=None):
835
"""Return the graph for this repository format"""
836
parents_provider = self._make_parents_provider(other_repository)
837
return graph.Graph(parents_provider)
839
def gather_stats(self, revid=None, committers=None):
840
"""See Repository.gather_stats()."""
841
path = self.bzrdir._path_for_remote_call(self._client)
842
# revid can be None to indicate no revisions, not just NULL_REVISION
843
if revid is None or revision.is_null(revid):
847
if committers is None or not committers:
848
fmt_committers = 'no'
850
fmt_committers = 'yes'
851
response_tuple, response_handler = self._call_expecting_body(
852
'Repository.gather_stats', path, fmt_revid, fmt_committers)
853
if response_tuple[0] != 'ok':
854
raise errors.UnexpectedSmartServerResponse(response_tuple)
856
body = response_handler.read_body_bytes()
858
for line in body.split('\n'):
861
key, val_text = line.split(':')
862
if key in ('revisions', 'size', 'committers'):
863
result[key] = int(val_text)
864
elif key in ('firstrev', 'latestrev'):
865
values = val_text.split(' ')[1:]
866
result[key] = (float(values[0]), long(values[1]))
870
def find_branches(self, using=False):
871
"""See Repository.find_branches()."""
872
# should be an API call to the server.
874
return self._real_repository.find_branches(using=using)
876
def get_physical_lock_status(self):
877
"""See Repository.get_physical_lock_status()."""
878
# should be an API call to the server.
880
return self._real_repository.get_physical_lock_status()
882
def is_in_write_group(self):
883
"""Return True if there is an open write group.
885
write groups are only applicable locally for the smart server..
887
if self._real_repository:
888
return self._real_repository.is_in_write_group()
891
return self._lock_count >= 1
894
"""See Repository.is_shared()."""
895
path = self.bzrdir._path_for_remote_call(self._client)
896
response = self._call('Repository.is_shared', path)
897
if response[0] not in ('yes', 'no'):
898
raise SmartProtocolError('unexpected response code %s' % (response,))
899
return response[0] == 'yes'
901
def is_write_locked(self):
902
return self._lock_mode == 'w'
905
# wrong eventually - want a local lock cache context
906
if not self._lock_mode:
907
self._lock_mode = 'r'
909
self._unstacked_provider.enable_cache(cache_misses=True)
910
if self._real_repository is not None:
911
self._real_repository.lock_read()
912
for repo in self._fallback_repositories:
915
self._lock_count += 1
917
def _remote_lock_write(self, token):
918
path = self.bzrdir._path_for_remote_call(self._client)
921
err_context = {'token': token}
922
response = self._call('Repository.lock_write', path, token,
924
if response[0] == 'ok':
928
raise errors.UnexpectedSmartServerResponse(response)
930
def lock_write(self, token=None, _skip_rpc=False):
931
if not self._lock_mode:
933
if self._lock_token is not None:
934
if token != self._lock_token:
935
raise errors.TokenMismatch(token, self._lock_token)
936
self._lock_token = token
938
self._lock_token = self._remote_lock_write(token)
939
# if self._lock_token is None, then this is something like packs or
940
# svn where we don't get to lock the repo, or a weave style repository
941
# where we cannot lock it over the wire and attempts to do so will
943
if self._real_repository is not None:
944
self._real_repository.lock_write(token=self._lock_token)
945
if token is not None:
946
self._leave_lock = True
948
self._leave_lock = False
949
self._lock_mode = 'w'
951
cache_misses = self._real_repository is None
952
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
953
for repo in self._fallback_repositories:
954
# Writes don't affect fallback repos
956
elif self._lock_mode == 'r':
957
raise errors.ReadOnlyError(self)
959
self._lock_count += 1
960
return self._lock_token or None
962
def leave_lock_in_place(self):
963
if not self._lock_token:
964
raise NotImplementedError(self.leave_lock_in_place)
965
self._leave_lock = True
967
def dont_leave_lock_in_place(self):
968
if not self._lock_token:
969
raise NotImplementedError(self.dont_leave_lock_in_place)
970
self._leave_lock = False
972
def _set_real_repository(self, repository):
973
"""Set the _real_repository for this repository.
975
:param repository: The repository to fallback to for non-hpss
976
implemented operations.
978
if self._real_repository is not None:
979
# Replacing an already set real repository.
980
# We cannot do this [currently] if the repository is locked -
981
# synchronised state might be lost.
983
raise AssertionError('_real_repository is already set')
984
if isinstance(repository, RemoteRepository):
985
raise AssertionError()
986
self._real_repository = repository
987
# three code paths happen here:
988
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
989
# up stacking. In this case self._fallback_repositories is [], and the
990
# real repo is already setup. Preserve the real repo and
991
# RemoteRepository.add_fallback_repository will avoid adding
993
# 2) new servers, RemoteBranch.open() sets up stacking, and when
994
# ensure_real is triggered from a branch, the real repository to
995
# set already has a matching list with separate instances, but
996
# as they are also RemoteRepositories we don't worry about making the
997
# lists be identical.
998
# 3) new servers, RemoteRepository.ensure_real is triggered before
999
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1000
# and need to populate it.
1001
if (self._fallback_repositories and
1002
len(self._real_repository._fallback_repositories) !=
1003
len(self._fallback_repositories)):
1004
if len(self._real_repository._fallback_repositories):
1005
raise AssertionError(
1006
"cannot cleanly remove existing _fallback_repositories")
1007
for fb in self._fallback_repositories:
1008
self._real_repository.add_fallback_repository(fb)
1009
if self._lock_mode == 'w':
1010
# if we are already locked, the real repository must be able to
1011
# acquire the lock with our token.
1012
self._real_repository.lock_write(self._lock_token)
1013
elif self._lock_mode == 'r':
1014
self._real_repository.lock_read()
1016
def start_write_group(self):
1017
"""Start a write group on the decorated repository.
1019
Smart methods perform operations in a single step so this API
1020
is not really applicable except as a compatibility thunk
1021
for older plugins that don't use e.g. the CommitBuilder
1025
return self._real_repository.start_write_group()
1027
def _unlock(self, token):
1028
path = self.bzrdir._path_for_remote_call(self._client)
1030
# with no token the remote repository is not persistently locked.
1032
err_context = {'token': token}
1033
response = self._call('Repository.unlock', path, token,
1035
if response == ('ok',):
1038
raise errors.UnexpectedSmartServerResponse(response)
1041
if not self._lock_count:
1042
return lock.cant_unlock_not_held(self)
1043
self._lock_count -= 1
1044
if self._lock_count > 0:
1046
self._unstacked_provider.disable_cache()
1047
old_mode = self._lock_mode
1048
self._lock_mode = None
1050
# The real repository is responsible at present for raising an
1051
# exception if it's in an unfinished write group. However, it
1052
# normally will *not* actually remove the lock from disk - that's
1053
# done by the server on receiving the Repository.unlock call.
1054
# This is just to let the _real_repository stay up to date.
1055
if self._real_repository is not None:
1056
self._real_repository.unlock()
1058
# The rpc-level lock should be released even if there was a
1059
# problem releasing the vfs-based lock.
1061
# Only write-locked repositories need to make a remote method
1062
# call to perform the unlock.
1063
old_token = self._lock_token
1064
self._lock_token = None
1065
if not self._leave_lock:
1066
self._unlock(old_token)
1067
# Fallbacks are always 'lock_read()' so we don't pay attention to
1069
for repo in self._fallback_repositories:
1072
def break_lock(self):
1073
# should hand off to the network
1075
return self._real_repository.break_lock()
1077
def _get_tarball(self, compression):
1078
"""Return a TemporaryFile containing a repository tarball.
1080
Returns None if the server does not support sending tarballs.
1083
path = self.bzrdir._path_for_remote_call(self._client)
1085
response, protocol = self._call_expecting_body(
1086
'Repository.tarball', path, compression)
1087
except errors.UnknownSmartMethod:
1088
protocol.cancel_read_body()
1090
if response[0] == 'ok':
1091
# Extract the tarball and return it
1092
t = tempfile.NamedTemporaryFile()
1093
# TODO: rpc layer should read directly into it...
1094
t.write(protocol.read_body_bytes())
1097
raise errors.UnexpectedSmartServerResponse(response)
1099
def sprout(self, to_bzrdir, revision_id=None):
1100
# TODO: Option to control what format is created?
1102
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1104
dest_repo.fetch(self, revision_id=revision_id)
1107
### These methods are just thin shims to the VFS object for now.
1109
def revision_tree(self, revision_id):
1111
return self._real_repository.revision_tree(revision_id)
1113
def get_serializer_format(self):
1115
return self._real_repository.get_serializer_format()
1117
def get_commit_builder(self, branch, parents, config, timestamp=None,
1118
timezone=None, committer=None, revprops=None,
1120
# FIXME: It ought to be possible to call this without immediately
1121
# triggering _ensure_real. For now it's the easiest thing to do.
1123
real_repo = self._real_repository
1124
builder = real_repo.get_commit_builder(branch, parents,
1125
config, timestamp=timestamp, timezone=timezone,
1126
committer=committer, revprops=revprops, revision_id=revision_id)
1129
def add_fallback_repository(self, repository):
1130
"""Add a repository to use for looking up data not held locally.
1132
:param repository: A repository.
1134
if not self._format.supports_external_lookups:
1135
raise errors.UnstackableRepositoryFormat(
1136
self._format.network_name(), self.base)
1137
# We need to accumulate additional repositories here, to pass them in
1140
if self.is_locked():
1141
# We will call fallback.unlock() when we transition to the unlocked
1142
# state, so always add a lock here. If a caller passes us a locked
1143
# repository, they are responsible for unlocking it later.
1144
repository.lock_read()
1145
self._fallback_repositories.append(repository)
1146
# If self._real_repository was parameterised already (e.g. because a
1147
# _real_branch had its get_stacked_on_url method called), then the
1148
# repository to be added may already be in the _real_repositories list.
1149
if self._real_repository is not None:
1150
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1151
self._real_repository._fallback_repositories]
1152
if repository.bzrdir.root_transport.base not in fallback_locations:
1153
self._real_repository.add_fallback_repository(repository)
1155
def add_inventory(self, revid, inv, parents):
1157
return self._real_repository.add_inventory(revid, inv, parents)
1159
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1162
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1163
delta, new_revision_id, parents)
1165
def add_revision(self, rev_id, rev, inv=None, config=None):
1167
return self._real_repository.add_revision(
1168
rev_id, rev, inv=inv, config=config)
1171
def get_inventory(self, revision_id):
1173
return self._real_repository.get_inventory(revision_id)
1175
def iter_inventories(self, revision_ids):
1177
return self._real_repository.iter_inventories(revision_ids)
1180
def get_revision(self, revision_id):
1182
return self._real_repository.get_revision(revision_id)
1184
def get_transaction(self):
1186
return self._real_repository.get_transaction()
1189
def clone(self, a_bzrdir, revision_id=None):
1191
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1193
def make_working_trees(self):
1194
"""See Repository.make_working_trees"""
1196
return self._real_repository.make_working_trees()
1198
def refresh_data(self):
1199
"""Re-read any data needed to to synchronise with disk.
1201
This method is intended to be called after another repository instance
1202
(such as one used by a smart server) has inserted data into the
1203
repository. It may not be called during a write group, but may be
1204
called at any other time.
1206
if self.is_in_write_group():
1207
raise errors.InternalBzrError(
1208
"May not refresh_data while in a write group.")
1209
if self._real_repository is not None:
1210
self._real_repository.refresh_data()
1212
def revision_ids_to_search_result(self, result_set):
1213
"""Convert a set of revision ids to a graph SearchResult."""
1214
result_parents = set()
1215
for parents in self.get_graph().get_parent_map(
1216
result_set).itervalues():
1217
result_parents.update(parents)
1218
included_keys = result_set.intersection(result_parents)
1219
start_keys = result_set.difference(included_keys)
1220
exclude_keys = result_parents.difference(result_set)
1221
result = graph.SearchResult(start_keys, exclude_keys,
1222
len(result_set), result_set)
1226
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1227
"""Return the revision ids that other has that this does not.
1229
These are returned in topological order.
1231
revision_id: only return revision ids included by revision_id.
1233
return repository.InterRepository.get(
1234
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1236
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1238
# No base implementation to use as RemoteRepository is not a subclass
1239
# of Repository; so this is a copy of Repository.fetch().
1240
if fetch_spec is not None and revision_id is not None:
1241
raise AssertionError(
1242
"fetch_spec and revision_id are mutually exclusive.")
1243
if self.is_in_write_group():
1244
raise errors.InternalBzrError(
1245
"May not fetch while in a write group.")
1246
# fast path same-url fetch operations
1247
if (self.has_same_location(source)
1248
and fetch_spec is None
1249
and self._has_same_fallbacks(source)):
1250
# check that last_revision is in 'from' and then return a
1252
if (revision_id is not None and
1253
not revision.is_null(revision_id)):
1254
self.get_revision(revision_id)
1256
# if there is no specific appropriate InterRepository, this will get
1257
# the InterRepository base class, which raises an
1258
# IncompatibleRepositories when asked to fetch.
1259
inter = repository.InterRepository.get(source, self)
1260
return inter.fetch(revision_id=revision_id, pb=pb,
1261
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1263
def create_bundle(self, target, base, fileobj, format=None):
1265
self._real_repository.create_bundle(target, base, fileobj, format)
1268
def get_ancestry(self, revision_id, topo_sorted=True):
1270
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1272
def fileids_altered_by_revision_ids(self, revision_ids):
1274
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1276
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1278
return self._real_repository._get_versioned_file_checker(
1279
revisions, revision_versions_cache)
1281
def iter_files_bytes(self, desired_files):
1282
"""See Repository.iter_file_bytes.
1285
return self._real_repository.iter_files_bytes(desired_files)
1287
def get_parent_map(self, revision_ids):
1288
"""See bzrlib.Graph.get_parent_map()."""
1289
return self._make_parents_provider().get_parent_map(revision_ids)
1291
def _get_parent_map_rpc(self, keys):
1292
"""Helper for get_parent_map that performs the RPC."""
1293
medium = self._client._medium
1294
if medium._is_remote_before((1, 2)):
1295
# We already found out that the server can't understand
1296
# Repository.get_parent_map requests, so just fetch the whole
1299
# Note that this reads the whole graph, when only some keys are
1300
# wanted. On this old server there's no way (?) to get them all
1301
# in one go, and the user probably will have seen a warning about
1302
# the server being old anyhow.
1303
rg = self._get_revision_graph(None)
1304
# There is an API discrepancy between get_parent_map and
1305
# get_revision_graph. Specifically, a "key:()" pair in
1306
# get_revision_graph just means a node has no parents. For
1307
# "get_parent_map" it means the node is a ghost. So fix up the
1308
# graph to correct this.
1309
# https://bugs.launchpad.net/bzr/+bug/214894
1310
# There is one other "bug" which is that ghosts in
1311
# get_revision_graph() are not returned at all. But we won't worry
1312
# about that for now.
1313
for node_id, parent_ids in rg.iteritems():
1314
if parent_ids == ():
1315
rg[node_id] = (NULL_REVISION,)
1316
rg[NULL_REVISION] = ()
1321
raise ValueError('get_parent_map(None) is not valid')
1322
if NULL_REVISION in keys:
1323
keys.discard(NULL_REVISION)
1324
found_parents = {NULL_REVISION:()}
1326
return found_parents
1329
# TODO(Needs analysis): We could assume that the keys being requested
1330
# from get_parent_map are in a breadth first search, so typically they
1331
# will all be depth N from some common parent, and we don't have to
1332
# have the server iterate from the root parent, but rather from the
1333
# keys we're searching; and just tell the server the keyspace we
1334
# already have; but this may be more traffic again.
1336
# Transform self._parents_map into a search request recipe.
1337
# TODO: Manage this incrementally to avoid covering the same path
1338
# repeatedly. (The server will have to on each request, but the less
1339
# work done the better).
1341
# Negative caching notes:
1342
# new server sends missing when a request including the revid
1343
# 'include-missing:' is present in the request.
1344
# missing keys are serialised as missing:X, and we then call
1345
# provider.note_missing(X) for-all X
1346
parents_map = self._unstacked_provider.get_cached_map()
1347
if parents_map is None:
1348
# Repository is not locked, so there's no cache.
1350
# start_set is all the keys in the cache
1351
start_set = set(parents_map)
1352
# result set is all the references to keys in the cache
1353
result_parents = set()
1354
for parents in parents_map.itervalues():
1355
result_parents.update(parents)
1356
stop_keys = result_parents.difference(start_set)
1357
# We don't need to send ghosts back to the server as a position to
1359
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1360
key_count = len(parents_map)
1361
if (NULL_REVISION in result_parents
1362
and NULL_REVISION in self._unstacked_provider.missing_keys):
1363
# If we pruned NULL_REVISION from the stop_keys because it's also
1364
# in our cache of "missing" keys we need to increment our key count
1365
# by 1, because the reconsitituted SearchResult on the server will
1366
# still consider NULL_REVISION to be an included key.
1368
included_keys = start_set.intersection(result_parents)
1369
start_set.difference_update(included_keys)
1370
recipe = ('manual', start_set, stop_keys, key_count)
1371
body = self._serialise_search_recipe(recipe)
1372
path = self.bzrdir._path_for_remote_call(self._client)
1374
if type(key) is not str:
1376
"key %r not a plain string" % (key,))
1377
verb = 'Repository.get_parent_map'
1378
args = (path, 'include-missing:') + tuple(keys)
1380
response = self._call_with_body_bytes_expecting_body(
1382
except errors.UnknownSmartMethod:
1383
# Server does not support this method, so get the whole graph.
1384
# Worse, we have to force a disconnection, because the server now
1385
# doesn't realise it has a body on the wire to consume, so the
1386
# only way to recover is to abandon the connection.
1388
'Server is too old for fast get_parent_map, reconnecting. '
1389
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1391
# To avoid having to disconnect repeatedly, we keep track of the
1392
# fact the server doesn't understand remote methods added in 1.2.
1393
medium._remember_remote_is_before((1, 2))
1394
# Recurse just once and we should use the fallback code.
1395
return self._get_parent_map_rpc(keys)
1396
response_tuple, response_handler = response
1397
if response_tuple[0] not in ['ok']:
1398
response_handler.cancel_read_body()
1399
raise errors.UnexpectedSmartServerResponse(response_tuple)
1400
if response_tuple[0] == 'ok':
1401
coded = bz2.decompress(response_handler.read_body_bytes())
1403
# no revisions found
1405
lines = coded.split('\n')
1408
d = tuple(line.split())
1410
revision_graph[d[0]] = d[1:]
1413
if d[0].startswith('missing:'):
1415
self._unstacked_provider.note_missing_key(revid)
1417
# no parents - so give the Graph result
1419
revision_graph[d[0]] = (NULL_REVISION,)
1420
return revision_graph
1423
def get_signature_text(self, revision_id):
1425
return self._real_repository.get_signature_text(revision_id)
1428
def get_inventory_xml(self, revision_id):
1430
return self._real_repository.get_inventory_xml(revision_id)
1432
def deserialise_inventory(self, revision_id, xml):
1434
return self._real_repository.deserialise_inventory(revision_id, xml)
1436
def reconcile(self, other=None, thorough=False):
1438
return self._real_repository.reconcile(other=other, thorough=thorough)
1440
def all_revision_ids(self):
1442
return self._real_repository.all_revision_ids()
1445
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1447
return self._real_repository.get_deltas_for_revisions(revisions,
1448
specific_fileids=specific_fileids)
1451
def get_revision_delta(self, revision_id, specific_fileids=None):
1453
return self._real_repository.get_revision_delta(revision_id,
1454
specific_fileids=specific_fileids)
1457
def revision_trees(self, revision_ids):
1459
return self._real_repository.revision_trees(revision_ids)
1462
def get_revision_reconcile(self, revision_id):
1464
return self._real_repository.get_revision_reconcile(revision_id)
1467
def check(self, revision_ids=None):
1469
return self._real_repository.check(revision_ids=revision_ids)
1471
def copy_content_into(self, destination, revision_id=None):
1473
return self._real_repository.copy_content_into(
1474
destination, revision_id=revision_id)
1476
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1477
# get a tarball of the remote repository, and copy from that into the
1479
from bzrlib import osutils
1481
# TODO: Maybe a progress bar while streaming the tarball?
1482
note("Copying repository content as tarball...")
1483
tar_file = self._get_tarball('bz2')
1484
if tar_file is None:
1486
destination = to_bzrdir.create_repository()
1488
tar = tarfile.open('repository', fileobj=tar_file,
1490
tmpdir = osutils.mkdtemp()
1492
_extract_tar(tar, tmpdir)
1493
tmp_bzrdir = BzrDir.open(tmpdir)
1494
tmp_repo = tmp_bzrdir.open_repository()
1495
tmp_repo.copy_content_into(destination, revision_id)
1497
osutils.rmtree(tmpdir)
1501
# TODO: Suggestion from john: using external tar is much faster than
1502
# python's tarfile library, but it may not work on windows.
1505
def inventories(self):
1506
"""Decorate the real repository for now.
1508
In the long term a full blown network facility is needed to
1509
avoid creating a real repository object locally.
1512
return self._real_repository.inventories
1515
def pack(self, hint=None):
1516
"""Compress the data within the repository.
1518
This is not currently implemented within the smart server.
1521
return self._real_repository.pack(hint=hint)
1524
def revisions(self):
1525
"""Decorate the real repository for now.
1527
In the short term this should become a real object to intercept graph
1530
In the long term a full blown network facility is needed.
1533
return self._real_repository.revisions
1535
def set_make_working_trees(self, new_value):
1537
new_value_str = "True"
1539
new_value_str = "False"
1540
path = self.bzrdir._path_for_remote_call(self._client)
1542
response = self._call(
1543
'Repository.set_make_working_trees', path, new_value_str)
1544
except errors.UnknownSmartMethod:
1546
self._real_repository.set_make_working_trees(new_value)
1548
if response[0] != 'ok':
1549
raise errors.UnexpectedSmartServerResponse(response)
1552
def signatures(self):
1553
"""Decorate the real repository for now.
1555
In the long term a full blown network facility is needed to avoid
1556
creating a real repository object locally.
1559
return self._real_repository.signatures
1562
def sign_revision(self, revision_id, gpg_strategy):
1564
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1568
"""Decorate the real repository for now.
1570
In the long term a full blown network facility is needed to avoid
1571
creating a real repository object locally.
1574
return self._real_repository.texts
1577
def get_revisions(self, revision_ids):
1579
return self._real_repository.get_revisions(revision_ids)
1581
def supports_rich_root(self):
1582
return self._format.rich_root_data
1584
def iter_reverse_revision_history(self, revision_id):
1586
return self._real_repository.iter_reverse_revision_history(revision_id)
1589
def _serializer(self):
1590
return self._format._serializer
1592
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1594
return self._real_repository.store_revision_signature(
1595
gpg_strategy, plaintext, revision_id)
1597
def add_signature_text(self, revision_id, signature):
1599
return self._real_repository.add_signature_text(revision_id, signature)
1601
def has_signature_for_revision_id(self, revision_id):
1603
return self._real_repository.has_signature_for_revision_id(revision_id)
1605
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1607
return self._real_repository.item_keys_introduced_by(revision_ids,
1608
_files_pb=_files_pb)
1610
def revision_graph_can_have_wrong_parents(self):
1611
# The answer depends on the remote repo format.
1613
return self._real_repository.revision_graph_can_have_wrong_parents()
1615
def _find_inconsistent_revision_parents(self):
1617
return self._real_repository._find_inconsistent_revision_parents()
1619
def _check_for_inconsistent_revision_parents(self):
1621
return self._real_repository._check_for_inconsistent_revision_parents()
1623
def _make_parents_provider(self, other=None):
1624
providers = [self._unstacked_provider]
1625
if other is not None:
1626
providers.insert(0, other)
1627
providers.extend(r._make_parents_provider() for r in
1628
self._fallback_repositories)
1629
return graph.StackedParentsProvider(providers)
1631
def _serialise_search_recipe(self, recipe):
1632
"""Serialise a graph search recipe.
1634
:param recipe: A search recipe (start, stop, count).
1635
:return: Serialised bytes.
1637
start_keys = ' '.join(recipe[1])
1638
stop_keys = ' '.join(recipe[2])
1639
count = str(recipe[3])
1640
return '\n'.join((start_keys, stop_keys, count))
1642
def _serialise_search_result(self, search_result):
1643
if isinstance(search_result, graph.PendingAncestryResult):
1644
parts = ['ancestry-of']
1645
parts.extend(search_result.heads)
1647
recipe = search_result.get_recipe()
1648
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1649
return '\n'.join(parts)
1652
path = self.bzrdir._path_for_remote_call(self._client)
1654
response = self._call('PackRepository.autopack', path)
1655
except errors.UnknownSmartMethod:
1657
self._real_repository._pack_collection.autopack()
1660
if response[0] != 'ok':
1661
raise errors.UnexpectedSmartServerResponse(response)
1664
class RemoteStreamSink(repository.StreamSink):
1666
def _insert_real(self, stream, src_format, resume_tokens):
1667
self.target_repo._ensure_real()
1668
sink = self.target_repo._real_repository._get_sink()
1669
result = sink.insert_stream(stream, src_format, resume_tokens)
1671
self.target_repo.autopack()
1674
def insert_stream(self, stream, src_format, resume_tokens):
1675
target = self.target_repo
1676
target._unstacked_provider.missing_keys.clear()
1677
if target._lock_token:
1678
verb = 'Repository.insert_stream_locked'
1679
extra_args = (target._lock_token or '',)
1680
required_version = (1, 14)
1682
verb = 'Repository.insert_stream'
1684
required_version = (1, 13)
1685
client = target._client
1686
medium = client._medium
1687
if medium._is_remote_before(required_version):
1688
# No possible way this can work.
1689
return self._insert_real(stream, src_format, resume_tokens)
1690
path = target.bzrdir._path_for_remote_call(client)
1691
if not resume_tokens:
1692
# XXX: Ugly but important for correctness, *will* be fixed during
1693
# 1.13 cycle. Pushing a stream that is interrupted results in a
1694
# fallback to the _real_repositories sink *with a partial stream*.
1695
# Thats bad because we insert less data than bzr expected. To avoid
1696
# this we do a trial push to make sure the verb is accessible, and
1697
# do not fallback when actually pushing the stream. A cleanup patch
1698
# is going to look at rewinding/restarting the stream/partial
1700
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1702
response = client.call_with_body_stream(
1703
(verb, path, '') + extra_args, byte_stream)
1704
except errors.UnknownSmartMethod:
1705
medium._remember_remote_is_before(required_version)
1706
return self._insert_real(stream, src_format, resume_tokens)
1707
byte_stream = smart_repo._stream_to_byte_stream(
1709
resume_tokens = ' '.join(resume_tokens)
1710
response = client.call_with_body_stream(
1711
(verb, path, resume_tokens) + extra_args, byte_stream)
1712
if response[0][0] not in ('ok', 'missing-basis'):
1713
raise errors.UnexpectedSmartServerResponse(response)
1714
if response[0][0] == 'missing-basis':
1715
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1716
resume_tokens = tokens
1717
return resume_tokens, set(missing_keys)
1719
self.target_repo.refresh_data()
1723
class RemoteStreamSource(repository.StreamSource):
1724
"""Stream data from a remote server."""
1726
def get_stream(self, search):
1727
if (self.from_repository._fallback_repositories and
1728
self.to_format._fetch_order == 'topological'):
1729
return self._real_stream(self.from_repository, search)
1730
return self.missing_parents_chain(search, [self.from_repository] +
1731
self.from_repository._fallback_repositories)
1733
def _real_stream(self, repo, search):
1734
"""Get a stream for search from repo.
1736
This never called RemoteStreamSource.get_stream, and is a heler
1737
for RemoteStreamSource._get_stream to allow getting a stream
1738
reliably whether fallback back because of old servers or trying
1739
to stream from a non-RemoteRepository (which the stacked support
1742
source = repo._get_source(self.to_format)
1743
if isinstance(source, RemoteStreamSource):
1744
return repository.StreamSource.get_stream(source, search)
1745
return source.get_stream(search)
1747
def _get_stream(self, repo, search):
1748
"""Core worker to get a stream from repo for search.
1750
This is used by both get_stream and the stacking support logic. It
1751
deliberately gets a stream for repo which does not need to be
1752
self.from_repository. In the event that repo is not Remote, or
1753
cannot do a smart stream, a fallback is made to the generic
1754
repository._get_stream() interface, via self._real_stream.
1756
In the event of stacking, streams from _get_stream will not
1757
contain all the data for search - this is normal (see get_stream).
1759
:param repo: A repository.
1760
:param search: A search.
1762
# Fallbacks may be non-smart
1763
if not isinstance(repo, RemoteRepository):
1764
return self._real_stream(repo, search)
1765
client = repo._client
1766
medium = client._medium
1767
if medium._is_remote_before((1, 13)):
1768
# streaming was added in 1.13
1769
return self._real_stream(repo, search)
1770
path = repo.bzrdir._path_for_remote_call(client)
1772
search_bytes = repo._serialise_search_result(search)
1773
response = repo._call_with_body_bytes_expecting_body(
1774
'Repository.get_stream',
1775
(path, self.to_format.network_name()), search_bytes)
1776
response_tuple, response_handler = response
1777
except errors.UnknownSmartMethod:
1778
medium._remember_remote_is_before((1,13))
1779
return self._real_stream(repo, search)
1780
if response_tuple[0] != 'ok':
1781
raise errors.UnexpectedSmartServerResponse(response_tuple)
1782
byte_stream = response_handler.read_streamed_body()
1783
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1784
if src_format.network_name() != repo._format.network_name():
1785
raise AssertionError(
1786
"Mismatched RemoteRepository and stream src %r, %r" % (
1787
src_format.network_name(), repo._format.network_name()))
1790
def missing_parents_chain(self, search, sources):
1791
"""Chain multiple streams together to handle stacking.
1793
:param search: The overall search to satisfy with streams.
1794
:param sources: A list of Repository objects to query.
1796
self.serialiser = self.to_format._serializer
1797
self.seen_revs = set()
1798
self.referenced_revs = set()
1799
# If there are heads in the search, or the key count is > 0, we are not
1801
while not search.is_empty() and len(sources) > 1:
1802
source = sources.pop(0)
1803
stream = self._get_stream(source, search)
1804
for kind, substream in stream:
1805
if kind != 'revisions':
1806
yield kind, substream
1808
yield kind, self.missing_parents_rev_handler(substream)
1809
search = search.refine(self.seen_revs, self.referenced_revs)
1810
self.seen_revs = set()
1811
self.referenced_revs = set()
1812
if not search.is_empty():
1813
for kind, stream in self._get_stream(sources[0], search):
1816
def missing_parents_rev_handler(self, substream):
1817
for content in substream:
1818
revision_bytes = content.get_bytes_as('fulltext')
1819
revision = self.serialiser.read_revision_from_string(revision_bytes)
1820
self.seen_revs.add(content.key[-1])
1821
self.referenced_revs.update(revision.parent_ids)
1825
class RemoteBranchLockableFiles(LockableFiles):
1826
"""A 'LockableFiles' implementation that talks to a smart server.
1828
This is not a public interface class.
1831
def __init__(self, bzrdir, _client):
1832
self.bzrdir = bzrdir
1833
self._client = _client
1834
self._need_find_modes = True
1835
LockableFiles.__init__(
1836
self, bzrdir.get_branch_transport(None),
1837
'lock', lockdir.LockDir)
1839
def _find_modes(self):
1840
# RemoteBranches don't let the client set the mode of control files.
1841
self._dir_mode = None
1842
self._file_mode = None
1845
class RemoteBranchFormat(branch.BranchFormat):
1847
def __init__(self, network_name=None):
1848
super(RemoteBranchFormat, self).__init__()
1849
self._matchingbzrdir = RemoteBzrDirFormat()
1850
self._matchingbzrdir.set_branch_format(self)
1851
self._custom_format = None
1852
self._network_name = network_name
1854
def __eq__(self, other):
1855
return (isinstance(other, RemoteBranchFormat) and
1856
self.__dict__ == other.__dict__)
1858
def _ensure_real(self):
1859
if self._custom_format is None:
1860
self._custom_format = branch.network_format_registry.get(
1863
def get_format_description(self):
1864
return 'Remote BZR Branch'
1866
def network_name(self):
1867
return self._network_name
1869
def open(self, a_bzrdir, ignore_fallbacks=False):
1870
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
1872
def _vfs_initialize(self, a_bzrdir):
1873
# Initialisation when using a local bzrdir object, or a non-vfs init
1874
# method is not available on the server.
1875
# self._custom_format is always set - the start of initialize ensures
1877
if isinstance(a_bzrdir, RemoteBzrDir):
1878
a_bzrdir._ensure_real()
1879
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
1881
# We assume the bzrdir is parameterised; it may not be.
1882
result = self._custom_format.initialize(a_bzrdir)
1883
if (isinstance(a_bzrdir, RemoteBzrDir) and
1884
not isinstance(result, RemoteBranch)):
1885
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
1888
def initialize(self, a_bzrdir):
1889
# 1) get the network name to use.
1890
if self._custom_format:
1891
network_name = self._custom_format.network_name()
1893
# Select the current bzrlib default and ask for that.
1894
reference_bzrdir_format = bzrdir.format_registry.get('default')()
1895
reference_format = reference_bzrdir_format.get_branch_format()
1896
self._custom_format = reference_format
1897
network_name = reference_format.network_name()
1898
# Being asked to create on a non RemoteBzrDir:
1899
if not isinstance(a_bzrdir, RemoteBzrDir):
1900
return self._vfs_initialize(a_bzrdir)
1901
medium = a_bzrdir._client._medium
1902
if medium._is_remote_before((1, 13)):
1903
return self._vfs_initialize(a_bzrdir)
1904
# Creating on a remote bzr dir.
1905
# 2) try direct creation via RPC
1906
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1907
verb = 'BzrDir.create_branch'
1909
response = a_bzrdir._call(verb, path, network_name)
1910
except errors.UnknownSmartMethod:
1911
# Fallback - use vfs methods
1912
medium._remember_remote_is_before((1, 13))
1913
return self._vfs_initialize(a_bzrdir)
1914
if response[0] != 'ok':
1915
raise errors.UnexpectedSmartServerResponse(response)
1916
# Turn the response into a RemoteRepository object.
1917
format = RemoteBranchFormat(network_name=response[1])
1918
repo_format = response_tuple_to_repo_format(response[3:])
1919
if response[2] == '':
1920
repo_bzrdir = a_bzrdir
1922
repo_bzrdir = RemoteBzrDir(
1923
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
1925
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
1926
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
1927
format=format, setup_stacking=False)
1928
# XXX: We know this is a new branch, so it must have revno 0, revid
1929
# NULL_REVISION. Creating the branch locked would make this be unable
1930
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
1931
remote_branch._last_revision_info_cache = 0, NULL_REVISION
1932
return remote_branch
1934
def make_tags(self, branch):
1936
return self._custom_format.make_tags(branch)
1938
def supports_tags(self):
1939
# Remote branches might support tags, but we won't know until we
1940
# access the real remote branch.
1942
return self._custom_format.supports_tags()
1944
def supports_stacking(self):
1946
return self._custom_format.supports_stacking()
1948
def supports_set_append_revisions_only(self):
1950
return self._custom_format.supports_set_append_revisions_only()
1953
class RemoteBranch(branch.Branch, _RpcHelper):
1954
"""Branch stored on a server accessed by HPSS RPC.
1956
At the moment most operations are mapped down to simple file operations.
1959
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
1960
_client=None, format=None, setup_stacking=True):
1961
"""Create a RemoteBranch instance.
1963
:param real_branch: An optional local implementation of the branch
1964
format, usually accessing the data via the VFS.
1965
:param _client: Private parameter for testing.
1966
:param format: A RemoteBranchFormat object, None to create one
1967
automatically. If supplied it should have a network_name already
1969
:param setup_stacking: If True make an RPC call to determine the
1970
stacked (or not) status of the branch. If False assume the branch
1973
# We intentionally don't call the parent class's __init__, because it
1974
# will try to assign to self.tags, which is a property in this subclass.
1975
# And the parent's __init__ doesn't do much anyway.
1976
self.bzrdir = remote_bzrdir
1977
if _client is not None:
1978
self._client = _client
1980
self._client = remote_bzrdir._client
1981
self.repository = remote_repository
1982
if real_branch is not None:
1983
self._real_branch = real_branch
1984
# Give the remote repository the matching real repo.
1985
real_repo = self._real_branch.repository
1986
if isinstance(real_repo, RemoteRepository):
1987
real_repo._ensure_real()
1988
real_repo = real_repo._real_repository
1989
self.repository._set_real_repository(real_repo)
1990
# Give the branch the remote repository to let fast-pathing happen.
1991
self._real_branch.repository = self.repository
1993
self._real_branch = None
1994
# Fill out expected attributes of branch for bzrlib API users.
1995
self._clear_cached_state()
1996
self.base = self.bzrdir.root_transport.base
1997
self._control_files = None
1998
self._lock_mode = None
1999
self._lock_token = None
2000
self._repo_lock_token = None
2001
self._lock_count = 0
2002
self._leave_lock = False
2003
# Setup a format: note that we cannot call _ensure_real until all the
2004
# attributes above are set: This code cannot be moved higher up in this
2007
self._format = RemoteBranchFormat()
2008
if real_branch is not None:
2009
self._format._network_name = \
2010
self._real_branch._format.network_name()
2012
self._format = format
2013
if not self._format._network_name:
2014
# Did not get from open_branchV2 - old server.
2016
self._format._network_name = \
2017
self._real_branch._format.network_name()
2018
self.tags = self._format.make_tags(self)
2019
# The base class init is not called, so we duplicate this:
2020
hooks = branch.Branch.hooks['open']
2023
self._is_stacked = False
2025
self._setup_stacking()
2027
def _setup_stacking(self):
2028
# configure stacking into the remote repository, by reading it from
2031
fallback_url = self.get_stacked_on_url()
2032
except (errors.NotStacked, errors.UnstackableBranchFormat,
2033
errors.UnstackableRepositoryFormat), e:
2035
self._is_stacked = True
2036
self._activate_fallback_location(fallback_url)
2038
def _get_config(self):
2039
return RemoteBranchConfig(self)
2041
def _get_real_transport(self):
2042
# if we try vfs access, return the real branch's vfs transport
2044
return self._real_branch._transport
2046
_transport = property(_get_real_transport)
2049
return "%s(%s)" % (self.__class__.__name__, self.base)
2053
def _ensure_real(self):
2054
"""Ensure that there is a _real_branch set.
2056
Used before calls to self._real_branch.
2058
if self._real_branch is None:
2059
if not vfs.vfs_enabled():
2060
raise AssertionError('smart server vfs must be enabled '
2061
'to use vfs implementation')
2062
self.bzrdir._ensure_real()
2063
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
2064
if self.repository._real_repository is None:
2065
# Give the remote repository the matching real repo.
2066
real_repo = self._real_branch.repository
2067
if isinstance(real_repo, RemoteRepository):
2068
real_repo._ensure_real()
2069
real_repo = real_repo._real_repository
2070
self.repository._set_real_repository(real_repo)
2071
# Give the real branch the remote repository to let fast-pathing
2073
self._real_branch.repository = self.repository
2074
if self._lock_mode == 'r':
2075
self._real_branch.lock_read()
2076
elif self._lock_mode == 'w':
2077
self._real_branch.lock_write(token=self._lock_token)
2079
def _translate_error(self, err, **context):
2080
self.repository._translate_error(err, branch=self, **context)
2082
def _clear_cached_state(self):
2083
super(RemoteBranch, self)._clear_cached_state()
2084
if self._real_branch is not None:
2085
self._real_branch._clear_cached_state()
2087
def _clear_cached_state_of_remote_branch_only(self):
2088
"""Like _clear_cached_state, but doesn't clear the cache of
2091
This is useful when falling back to calling a method of
2092
self._real_branch that changes state. In that case the underlying
2093
branch changes, so we need to invalidate this RemoteBranch's cache of
2094
it. However, there's no need to invalidate the _real_branch's cache
2095
too, in fact doing so might harm performance.
2097
super(RemoteBranch, self)._clear_cached_state()
2100
def control_files(self):
2101
# Defer actually creating RemoteBranchLockableFiles until its needed,
2102
# because it triggers an _ensure_real that we otherwise might not need.
2103
if self._control_files is None:
2104
self._control_files = RemoteBranchLockableFiles(
2105
self.bzrdir, self._client)
2106
return self._control_files
2108
def _get_checkout_format(self):
2110
return self._real_branch._get_checkout_format()
2112
def get_physical_lock_status(self):
2113
"""See Branch.get_physical_lock_status()."""
2114
# should be an API call to the server, as branches must be lockable.
2116
return self._real_branch.get_physical_lock_status()
2118
def get_stacked_on_url(self):
2119
"""Get the URL this branch is stacked against.
2121
:raises NotStacked: If the branch is not stacked.
2122
:raises UnstackableBranchFormat: If the branch does not support
2124
:raises UnstackableRepositoryFormat: If the repository does not support
2128
# there may not be a repository yet, so we can't use
2129
# self._translate_error, so we can't use self._call either.
2130
response = self._client.call('Branch.get_stacked_on_url',
2131
self._remote_path())
2132
except errors.ErrorFromSmartServer, err:
2133
# there may not be a repository yet, so we can't call through
2134
# its _translate_error
2135
_translate_error(err, branch=self)
2136
except errors.UnknownSmartMethod, err:
2138
return self._real_branch.get_stacked_on_url()
2139
if response[0] != 'ok':
2140
raise errors.UnexpectedSmartServerResponse(response)
2143
def set_stacked_on_url(self, url):
2144
branch.Branch.set_stacked_on_url(self, url)
2146
self._is_stacked = False
2148
self._is_stacked = True
2150
def _vfs_get_tags_bytes(self):
2152
return self._real_branch._get_tags_bytes()
2154
def _get_tags_bytes(self):
2155
medium = self._client._medium
2156
if medium._is_remote_before((1, 13)):
2157
return self._vfs_get_tags_bytes()
2159
response = self._call('Branch.get_tags_bytes', self._remote_path())
2160
except errors.UnknownSmartMethod:
2161
medium._remember_remote_is_before((1, 13))
2162
return self._vfs_get_tags_bytes()
2165
def lock_read(self):
2166
self.repository.lock_read()
2167
if not self._lock_mode:
2168
self._lock_mode = 'r'
2169
self._lock_count = 1
2170
if self._real_branch is not None:
2171
self._real_branch.lock_read()
2173
self._lock_count += 1
2175
def _remote_lock_write(self, token):
2177
branch_token = repo_token = ''
2179
branch_token = token
2180
repo_token = self.repository.lock_write()
2181
self.repository.unlock()
2182
err_context = {'token': token}
2183
response = self._call(
2184
'Branch.lock_write', self._remote_path(), branch_token,
2185
repo_token or '', **err_context)
2186
if response[0] != 'ok':
2187
raise errors.UnexpectedSmartServerResponse(response)
2188
ok, branch_token, repo_token = response
2189
return branch_token, repo_token
2191
def lock_write(self, token=None):
2192
if not self._lock_mode:
2193
# Lock the branch and repo in one remote call.
2194
remote_tokens = self._remote_lock_write(token)
2195
self._lock_token, self._repo_lock_token = remote_tokens
2196
if not self._lock_token:
2197
raise SmartProtocolError('Remote server did not return a token!')
2198
# Tell the self.repository object that it is locked.
2199
self.repository.lock_write(
2200
self._repo_lock_token, _skip_rpc=True)
2202
if self._real_branch is not None:
2203
self._real_branch.lock_write(token=self._lock_token)
2204
if token is not None:
2205
self._leave_lock = True
2207
self._leave_lock = False
2208
self._lock_mode = 'w'
2209
self._lock_count = 1
2210
elif self._lock_mode == 'r':
2211
raise errors.ReadOnlyTransaction
2213
if token is not None:
2214
# A token was given to lock_write, and we're relocking, so
2215
# check that the given token actually matches the one we
2217
if token != self._lock_token:
2218
raise errors.TokenMismatch(token, self._lock_token)
2219
self._lock_count += 1
2220
# Re-lock the repository too.
2221
self.repository.lock_write(self._repo_lock_token)
2222
return self._lock_token or None
2224
def _set_tags_bytes(self, bytes):
2226
return self._real_branch._set_tags_bytes(bytes)
2228
def _unlock(self, branch_token, repo_token):
2229
err_context = {'token': str((branch_token, repo_token))}
2230
response = self._call(
2231
'Branch.unlock', self._remote_path(), branch_token,
2232
repo_token or '', **err_context)
2233
if response == ('ok',):
2235
raise errors.UnexpectedSmartServerResponse(response)
2239
self._lock_count -= 1
2240
if not self._lock_count:
2241
self._clear_cached_state()
2242
mode = self._lock_mode
2243
self._lock_mode = None
2244
if self._real_branch is not None:
2245
if (not self._leave_lock and mode == 'w' and
2246
self._repo_lock_token):
2247
# If this RemoteBranch will remove the physical lock
2248
# for the repository, make sure the _real_branch
2249
# doesn't do it first. (Because the _real_branch's
2250
# repository is set to be the RemoteRepository.)
2251
self._real_branch.repository.leave_lock_in_place()
2252
self._real_branch.unlock()
2254
# Only write-locked branched need to make a remote method
2255
# call to perform the unlock.
2257
if not self._lock_token:
2258
raise AssertionError('Locked, but no token!')
2259
branch_token = self._lock_token
2260
repo_token = self._repo_lock_token
2261
self._lock_token = None
2262
self._repo_lock_token = None
2263
if not self._leave_lock:
2264
self._unlock(branch_token, repo_token)
2266
self.repository.unlock()
2268
def break_lock(self):
2270
return self._real_branch.break_lock()
2272
def leave_lock_in_place(self):
2273
if not self._lock_token:
2274
raise NotImplementedError(self.leave_lock_in_place)
2275
self._leave_lock = True
2277
def dont_leave_lock_in_place(self):
2278
if not self._lock_token:
2279
raise NotImplementedError(self.dont_leave_lock_in_place)
2280
self._leave_lock = False
2282
def get_rev_id(self, revno, history=None):
2284
return _mod_revision.NULL_REVISION
2285
last_revision_info = self.last_revision_info()
2286
ok, result = self.repository.get_rev_id_for_revno(
2287
revno, last_revision_info)
2290
missing_parent = result[1]
2291
# Either the revision named by the server is missing, or its parent
2292
# is. Call get_parent_map to determine which, so that we report a
2294
parent_map = self.repository.get_parent_map([missing_parent])
2295
if missing_parent in parent_map:
2296
missing_parent = parent_map[missing_parent]
2297
raise errors.RevisionNotPresent(missing_parent, self.repository)
2299
def _last_revision_info(self):
2300
response = self._call('Branch.last_revision_info', self._remote_path())
2301
if response[0] != 'ok':
2302
raise SmartProtocolError('unexpected response code %s' % (response,))
2303
revno = int(response[1])
2304
last_revision = response[2]
2305
return (revno, last_revision)
2307
def _gen_revision_history(self):
2308
"""See Branch._gen_revision_history()."""
2309
if self._is_stacked:
2311
return self._real_branch._gen_revision_history()
2312
response_tuple, response_handler = self._call_expecting_body(
2313
'Branch.revision_history', self._remote_path())
2314
if response_tuple[0] != 'ok':
2315
raise errors.UnexpectedSmartServerResponse(response_tuple)
2316
result = response_handler.read_body_bytes().split('\x00')
2321
def _remote_path(self):
2322
return self.bzrdir._path_for_remote_call(self._client)
2324
def _set_last_revision_descendant(self, revision_id, other_branch,
2325
allow_diverged=False, allow_overwrite_descendant=False):
2326
# This performs additional work to meet the hook contract; while its
2327
# undesirable, we have to synthesise the revno to call the hook, and
2328
# not calling the hook is worse as it means changes can't be prevented.
2329
# Having calculated this though, we can't just call into
2330
# set_last_revision_info as a simple call, because there is a set_rh
2331
# hook that some folk may still be using.
2332
old_revno, old_revid = self.last_revision_info()
2333
history = self._lefthand_history(revision_id)
2334
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2335
err_context = {'other_branch': other_branch}
2336
response = self._call('Branch.set_last_revision_ex',
2337
self._remote_path(), self._lock_token, self._repo_lock_token,
2338
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2340
self._clear_cached_state()
2341
if len(response) != 3 and response[0] != 'ok':
2342
raise errors.UnexpectedSmartServerResponse(response)
2343
new_revno, new_revision_id = response[1:]
2344
self._last_revision_info_cache = new_revno, new_revision_id
2345
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2346
if self._real_branch is not None:
2347
cache = new_revno, new_revision_id
2348
self._real_branch._last_revision_info_cache = cache
2350
def _set_last_revision(self, revision_id):
2351
old_revno, old_revid = self.last_revision_info()
2352
# This performs additional work to meet the hook contract; while its
2353
# undesirable, we have to synthesise the revno to call the hook, and
2354
# not calling the hook is worse as it means changes can't be prevented.
2355
# Having calculated this though, we can't just call into
2356
# set_last_revision_info as a simple call, because there is a set_rh
2357
# hook that some folk may still be using.
2358
history = self._lefthand_history(revision_id)
2359
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2360
self._clear_cached_state()
2361
response = self._call('Branch.set_last_revision',
2362
self._remote_path(), self._lock_token, self._repo_lock_token,
2364
if response != ('ok',):
2365
raise errors.UnexpectedSmartServerResponse(response)
2366
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2369
def set_revision_history(self, rev_history):
2370
# Send just the tip revision of the history; the server will generate
2371
# the full history from that. If the revision doesn't exist in this
2372
# branch, NoSuchRevision will be raised.
2373
if rev_history == []:
2376
rev_id = rev_history[-1]
2377
self._set_last_revision(rev_id)
2378
for hook in branch.Branch.hooks['set_rh']:
2379
hook(self, rev_history)
2380
self._cache_revision_history(rev_history)
2382
def _get_parent_location(self):
2383
medium = self._client._medium
2384
if medium._is_remote_before((1, 13)):
2385
return self._vfs_get_parent_location()
2387
response = self._call('Branch.get_parent', self._remote_path())
2388
except errors.UnknownSmartMethod:
2389
medium._remember_remote_is_before((1, 13))
2390
return self._vfs_get_parent_location()
2391
if len(response) != 1:
2392
raise errors.UnexpectedSmartServerResponse(response)
2393
parent_location = response[0]
2394
if parent_location == '':
2396
return parent_location
2398
def _vfs_get_parent_location(self):
2400
return self._real_branch._get_parent_location()
2402
def _set_parent_location(self, url):
2403
medium = self._client._medium
2404
if medium._is_remote_before((1, 15)):
2405
return self._vfs_set_parent_location(url)
2407
call_url = url or ''
2408
if type(call_url) is not str:
2409
raise AssertionError('url must be a str or None (%s)' % url)
2410
response = self._call('Branch.set_parent_location',
2411
self._remote_path(), self._lock_token, self._repo_lock_token,
2413
except errors.UnknownSmartMethod:
2414
medium._remember_remote_is_before((1, 15))
2415
return self._vfs_set_parent_location(url)
2417
raise errors.UnexpectedSmartServerResponse(response)
2419
def _vfs_set_parent_location(self, url):
2421
return self._real_branch._set_parent_location(url)
2424
def pull(self, source, overwrite=False, stop_revision=None,
2426
self._clear_cached_state_of_remote_branch_only()
2428
return self._real_branch.pull(
2429
source, overwrite=overwrite, stop_revision=stop_revision,
2430
_override_hook_target=self, **kwargs)
2433
def push(self, target, overwrite=False, stop_revision=None):
2435
return self._real_branch.push(
2436
target, overwrite=overwrite, stop_revision=stop_revision,
2437
_override_hook_source_branch=self)
2439
def is_locked(self):
2440
return self._lock_count >= 1
2443
def revision_id_to_revno(self, revision_id):
2445
return self._real_branch.revision_id_to_revno(revision_id)
2448
def set_last_revision_info(self, revno, revision_id):
2449
# XXX: These should be returned by the set_last_revision_info verb
2450
old_revno, old_revid = self.last_revision_info()
2451
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2452
revision_id = ensure_null(revision_id)
2454
response = self._call('Branch.set_last_revision_info',
2455
self._remote_path(), self._lock_token, self._repo_lock_token,
2456
str(revno), revision_id)
2457
except errors.UnknownSmartMethod:
2459
self._clear_cached_state_of_remote_branch_only()
2460
self._real_branch.set_last_revision_info(revno, revision_id)
2461
self._last_revision_info_cache = revno, revision_id
2463
if response == ('ok',):
2464
self._clear_cached_state()
2465
self._last_revision_info_cache = revno, revision_id
2466
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2467
# Update the _real_branch's cache too.
2468
if self._real_branch is not None:
2469
cache = self._last_revision_info_cache
2470
self._real_branch._last_revision_info_cache = cache
2472
raise errors.UnexpectedSmartServerResponse(response)
2475
def generate_revision_history(self, revision_id, last_rev=None,
2477
medium = self._client._medium
2478
if not medium._is_remote_before((1, 6)):
2479
# Use a smart method for 1.6 and above servers
2481
self._set_last_revision_descendant(revision_id, other_branch,
2482
allow_diverged=True, allow_overwrite_descendant=True)
2484
except errors.UnknownSmartMethod:
2485
medium._remember_remote_is_before((1, 6))
2486
self._clear_cached_state_of_remote_branch_only()
2487
self.set_revision_history(self._lefthand_history(revision_id,
2488
last_rev=last_rev,other_branch=other_branch))
2490
def set_push_location(self, location):
2492
return self._real_branch.set_push_location(location)
2495
class RemoteConfig(object):
2496
"""A Config that reads and writes from smart verbs.
2498
It is a low-level object that considers config data to be name/value pairs
2499
that may be associated with a section. Assigning meaning to the these
2500
values is done at higher levels like bzrlib.config.TreeConfig.
2503
def get_option(self, name, section=None, default=None):
2504
"""Return the value associated with a named option.
2506
:param name: The name of the value
2507
:param section: The section the option is in (if any)
2508
:param default: The value to return if the value is not set
2509
:return: The value or default value
2512
configobj = self._get_configobj()
2514
section_obj = configobj
2517
section_obj = configobj[section]
2520
return section_obj.get(name, default)
2521
except errors.UnknownSmartMethod:
2522
return self._vfs_get_option(name, section, default)
2524
def _response_to_configobj(self, response):
2525
if len(response[0]) and response[0][0] != 'ok':
2526
raise errors.UnexpectedSmartServerResponse(response)
2527
lines = response[1].read_body_bytes().splitlines()
2528
return config.ConfigObj(lines, encoding='utf-8')
2531
class RemoteBranchConfig(RemoteConfig):
2532
"""A RemoteConfig for Branches."""
2534
def __init__(self, branch):
2535
self._branch = branch
2537
def _get_configobj(self):
2538
path = self._branch._remote_path()
2539
response = self._branch._client.call_expecting_body(
2540
'Branch.get_config_file', path)
2541
return self._response_to_configobj(response)
2543
def set_option(self, value, name, section=None):
2544
"""Set the value associated with a named option.
2546
:param value: The value to set
2547
:param name: The name of the value to set
2548
:param section: The section the option is in (if any)
2550
medium = self._branch._client._medium
2551
if medium._is_remote_before((1, 14)):
2552
return self._vfs_set_option(value, name, section)
2554
path = self._branch._remote_path()
2555
response = self._branch._client.call('Branch.set_config_option',
2556
path, self._branch._lock_token, self._branch._repo_lock_token,
2557
value.encode('utf8'), name, section or '')
2558
except errors.UnknownSmartMethod:
2559
medium._remember_remote_is_before((1, 14))
2560
return self._vfs_set_option(value, name, section)
2562
raise errors.UnexpectedSmartServerResponse(response)
2564
def _real_object(self):
2565
self._branch._ensure_real()
2566
return self._branch._real_branch
2568
def _vfs_set_option(self, value, name, section=None):
2569
return self._real_object()._get_config().set_option(
2570
value, name, section)
2573
class RemoteBzrDirConfig(RemoteConfig):
2574
"""A RemoteConfig for BzrDirs."""
2576
def __init__(self, bzrdir):
2577
self._bzrdir = bzrdir
2579
def _get_configobj(self):
2580
medium = self._bzrdir._client._medium
2581
verb = 'BzrDir.get_config_file'
2582
if medium._is_remote_before((1, 15)):
2583
raise errors.UnknownSmartMethod(verb)
2584
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2585
response = self._bzrdir._call_expecting_body(
2587
return self._response_to_configobj(response)
2589
def _vfs_get_option(self, name, section, default):
2590
return self._real_object()._get_config().get_option(
2591
name, section, default)
2593
def set_option(self, value, name, section=None):
2594
"""Set the value associated with a named option.
2596
:param value: The value to set
2597
:param name: The name of the value to set
2598
:param section: The section the option is in (if any)
2600
return self._real_object()._get_config().set_option(
2601
value, name, section)
2603
def _real_object(self):
2604
self._bzrdir._ensure_real()
2605
return self._bzrdir._real_bzrdir
2609
def _extract_tar(tar, to_dir):
2610
"""Extract all the contents of a tarfile object.
2612
A replacement for extractall, which is not present in python2.4
2615
tar.extract(tarinfo, to_dir)
2618
def _translate_error(err, **context):
2619
"""Translate an ErrorFromSmartServer into a more useful error.
2621
Possible context keys:
2629
If the error from the server doesn't match a known pattern, then
2630
UnknownErrorFromSmartServer is raised.
2634
return context[name]
2635
except KeyError, key_err:
2636
mutter('Missing key %r in context %r', key_err.args[0], context)
2639
"""Get the path from the context if present, otherwise use first error
2643
return context['path']
2644
except KeyError, key_err:
2646
return err.error_args[0]
2647
except IndexError, idx_err:
2649
'Missing key %r in context %r', key_err.args[0], context)
2652
if err.error_verb == 'NoSuchRevision':
2653
raise NoSuchRevision(find('branch'), err.error_args[0])
2654
elif err.error_verb == 'nosuchrevision':
2655
raise NoSuchRevision(find('repository'), err.error_args[0])
2656
elif err.error_tuple == ('nobranch',):
2657
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2658
elif err.error_verb == 'norepository':
2659
raise errors.NoRepositoryPresent(find('bzrdir'))
2660
elif err.error_verb == 'LockContention':
2661
raise errors.LockContention('(remote lock)')
2662
elif err.error_verb == 'UnlockableTransport':
2663
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2664
elif err.error_verb == 'LockFailed':
2665
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2666
elif err.error_verb == 'TokenMismatch':
2667
raise errors.TokenMismatch(find('token'), '(remote token)')
2668
elif err.error_verb == 'Diverged':
2669
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2670
elif err.error_verb == 'TipChangeRejected':
2671
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2672
elif err.error_verb == 'UnstackableBranchFormat':
2673
raise errors.UnstackableBranchFormat(*err.error_args)
2674
elif err.error_verb == 'UnstackableRepositoryFormat':
2675
raise errors.UnstackableRepositoryFormat(*err.error_args)
2676
elif err.error_verb == 'NotStacked':
2677
raise errors.NotStacked(branch=find('branch'))
2678
elif err.error_verb == 'PermissionDenied':
2680
if len(err.error_args) >= 2:
2681
extra = err.error_args[1]
2684
raise errors.PermissionDenied(path, extra=extra)
2685
elif err.error_verb == 'ReadError':
2687
raise errors.ReadError(path)
2688
elif err.error_verb == 'NoSuchFile':
2690
raise errors.NoSuchFile(path)
2691
elif err.error_verb == 'FileExists':
2692
raise errors.FileExists(err.error_args[0])
2693
elif err.error_verb == 'DirectoryNotEmpty':
2694
raise errors.DirectoryNotEmpty(err.error_args[0])
2695
elif err.error_verb == 'ShortReadvError':
2696
args = err.error_args
2697
raise errors.ShortReadvError(
2698
args[0], int(args[1]), int(args[2]), int(args[3]))
2699
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2700
encoding = str(err.error_args[0]) # encoding must always be a string
2701
val = err.error_args[1]
2702
start = int(err.error_args[2])
2703
end = int(err.error_args[3])
2704
reason = str(err.error_args[4]) # reason must always be a string
2705
if val.startswith('u:'):
2706
val = val[2:].decode('utf-8')
2707
elif val.startswith('s:'):
2708
val = val[2:].decode('base64')
2709
if err.error_verb == 'UnicodeDecodeError':
2710
raise UnicodeDecodeError(encoding, val, start, end, reason)
2711
elif err.error_verb == 'UnicodeEncodeError':
2712
raise UnicodeEncodeError(encoding, val, start, end, reason)
2713
elif err.error_verb == 'ReadOnlyError':
2714
raise errors.TransportNotPossible('readonly transport')
2715
raise errors.UnknownErrorFromSmartServer(err)