1
# Copyright (C) 2006-2010 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,
35
from bzrlib.branch import BranchReferenceFormat
36
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
37
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
38
from bzrlib.errors import (
42
from bzrlib.lockable_files import LockableFiles
43
from bzrlib.smart import client, vfs, repository as smart_repo
44
from bzrlib.revision import ensure_null, NULL_REVISION
45
from bzrlib.trace import mutter, note, warning
48
class _RpcHelper(object):
49
"""Mixin class that helps with issuing RPCs."""
51
def _call(self, method, *args, **err_context):
53
return self._client.call(method, *args)
54
except errors.ErrorFromSmartServer, err:
55
self._translate_error(err, **err_context)
57
def _call_expecting_body(self, method, *args, **err_context):
59
return self._client.call_expecting_body(method, *args)
60
except errors.ErrorFromSmartServer, err:
61
self._translate_error(err, **err_context)
63
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
65
return self._client.call_with_body_bytes(method, args, body_bytes)
66
except errors.ErrorFromSmartServer, err:
67
self._translate_error(err, **err_context)
69
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
72
return self._client.call_with_body_bytes_expecting_body(
73
method, args, body_bytes)
74
except errors.ErrorFromSmartServer, err:
75
self._translate_error(err, **err_context)
78
def response_tuple_to_repo_format(response):
79
"""Convert a response tuple describing a repository format to a format."""
80
format = RemoteRepositoryFormat()
81
format._rich_root_data = (response[0] == 'yes')
82
format._supports_tree_reference = (response[1] == 'yes')
83
format._supports_external_lookups = (response[2] == 'yes')
84
format._network_name = response[3]
88
# Note: RemoteBzrDirFormat is in bzrdir.py
90
class RemoteBzrDir(BzrDir, _RpcHelper):
91
"""Control directory on a remote server, accessed via bzr:// or similar."""
93
def __init__(self, transport, format, _client=None, _force_probe=False):
94
"""Construct a RemoteBzrDir.
96
:param _client: Private parameter for testing. Disables probing and the
99
BzrDir.__init__(self, transport, format)
100
# this object holds a delegated bzrdir that uses file-level operations
101
# to talk to the other side
102
self._real_bzrdir = None
103
self._has_working_tree = 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
119
return '%s(%r)' % (self.__class__.__name__, self._client)
121
def _probe_bzrdir(self):
122
medium = self._client._medium
123
path = self._path_for_remote_call(self._client)
124
if medium._is_remote_before((2, 1)):
128
self._rpc_open_2_1(path)
130
except errors.UnknownSmartMethod:
131
medium._remember_remote_is_before((2, 1))
134
def _rpc_open_2_1(self, path):
135
response = self._call('BzrDir.open_2.1', path)
136
if response == ('no',):
137
raise errors.NotBranchError(path=self.root_transport.base)
138
elif response[0] == 'yes':
139
if response[1] == 'yes':
140
self._has_working_tree = True
141
elif response[1] == 'no':
142
self._has_working_tree = False
144
raise errors.UnexpectedSmartServerResponse(response)
146
raise errors.UnexpectedSmartServerResponse(response)
148
def _rpc_open(self, path):
149
response = self._call('BzrDir.open', path)
150
if response not in [('yes',), ('no',)]:
151
raise errors.UnexpectedSmartServerResponse(response)
152
if response == ('no',):
153
raise errors.NotBranchError(path=self.root_transport.base)
155
def _ensure_real(self):
156
"""Ensure that there is a _real_bzrdir set.
158
Used before calls to self._real_bzrdir.
160
if not self._real_bzrdir:
161
if 'hpssvfs' in debug.debug_flags:
163
warning('VFS BzrDir access triggered\n%s',
164
''.join(traceback.format_stack()))
165
self._real_bzrdir = BzrDir.open_from_transport(
166
self.root_transport, _server_formats=False)
167
self._format._network_name = \
168
self._real_bzrdir._format.network_name()
170
def _translate_error(self, err, **context):
171
_translate_error(err, bzrdir=self, **context)
173
def break_lock(self):
174
# Prevent aliasing problems in the next_open_branch_result cache.
175
# See create_branch for rationale.
176
self._next_open_branch_result = None
177
return BzrDir.break_lock(self)
179
def _vfs_cloning_metadir(self, require_stacking=False):
181
return self._real_bzrdir.cloning_metadir(
182
require_stacking=require_stacking)
184
def cloning_metadir(self, require_stacking=False):
185
medium = self._client._medium
186
if medium._is_remote_before((1, 13)):
187
return self._vfs_cloning_metadir(require_stacking=require_stacking)
188
verb = 'BzrDir.cloning_metadir'
193
path = self._path_for_remote_call(self._client)
195
response = self._call(verb, path, stacking)
196
except errors.UnknownSmartMethod:
197
medium._remember_remote_is_before((1, 13))
198
return self._vfs_cloning_metadir(require_stacking=require_stacking)
199
except errors.UnknownErrorFromSmartServer, err:
200
if err.error_tuple != ('BranchReference',):
202
# We need to resolve the branch reference to determine the
203
# cloning_metadir. This causes unnecessary RPCs to open the
204
# referenced branch (and bzrdir, etc) but only when the caller
205
# didn't already resolve the branch reference.
206
referenced_branch = self.open_branch()
207
return referenced_branch.bzrdir.cloning_metadir()
208
if len(response) != 3:
209
raise errors.UnexpectedSmartServerResponse(response)
210
control_name, repo_name, branch_info = response
211
if len(branch_info) != 2:
212
raise errors.UnexpectedSmartServerResponse(response)
213
branch_ref, branch_name = branch_info
214
format = bzrdir.network_format_registry.get(control_name)
216
format.repository_format = repository.network_format_registry.get(
218
if branch_ref == 'ref':
219
# XXX: we need possible_transports here to avoid reopening the
220
# connection to the referenced location
221
ref_bzrdir = BzrDir.open(branch_name)
222
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
223
format.set_branch_format(branch_format)
224
elif branch_ref == 'branch':
226
format.set_branch_format(
227
branch.network_format_registry.get(branch_name))
229
raise errors.UnexpectedSmartServerResponse(response)
232
def create_repository(self, shared=False):
233
# as per meta1 formats - just delegate to the format object which may
235
result = self._format.repository_format.initialize(self, shared)
236
if not isinstance(result, RemoteRepository):
237
return self.open_repository()
241
def destroy_repository(self):
242
"""See BzrDir.destroy_repository"""
244
self._real_bzrdir.destroy_repository()
246
def create_branch(self, name=None):
247
# as per meta1 formats - just delegate to the format object which may
249
real_branch = self._format.get_branch_format().initialize(self,
251
if not isinstance(real_branch, RemoteBranch):
252
result = RemoteBranch(self, self.find_repository(), real_branch,
256
# BzrDir.clone_on_transport() uses the result of create_branch but does
257
# not return it to its callers; we save approximately 8% of our round
258
# trips by handing the branch we created back to the first caller to
259
# open_branch rather than probing anew. Long term we need a API in
260
# bzrdir that doesn't discard result objects (like result_branch).
262
self._next_open_branch_result = result
265
def destroy_branch(self, name=None):
266
"""See BzrDir.destroy_branch"""
268
self._real_bzrdir.destroy_branch(name=name)
269
self._next_open_branch_result = None
271
def create_workingtree(self, revision_id=None, from_branch=None):
272
raise errors.NotLocalUrl(self.transport.base)
274
def find_branch_format(self):
275
"""Find the branch 'format' for this bzrdir.
277
This might be a synthetic object for e.g. RemoteBranch and SVN.
279
b = self.open_branch()
282
def get_branch_reference(self):
283
"""See BzrDir.get_branch_reference()."""
284
response = self._get_branch_reference()
285
if response[0] == 'ref':
290
def _get_branch_reference(self):
291
path = self._path_for_remote_call(self._client)
292
medium = self._client._medium
294
('BzrDir.open_branchV3', (2, 1)),
295
('BzrDir.open_branchV2', (1, 13)),
296
('BzrDir.open_branch', None),
298
for verb, required_version in candidate_calls:
299
if required_version and medium._is_remote_before(required_version):
302
response = self._call(verb, path)
303
except errors.UnknownSmartMethod:
304
if required_version is None:
306
medium._remember_remote_is_before(required_version)
309
if verb == 'BzrDir.open_branch':
310
if response[0] != 'ok':
311
raise errors.UnexpectedSmartServerResponse(response)
312
if response[1] != '':
313
return ('ref', response[1])
315
return ('branch', '')
316
if response[0] not in ('ref', 'branch'):
317
raise errors.UnexpectedSmartServerResponse(response)
320
def _get_tree_branch(self):
321
"""See BzrDir._get_tree_branch()."""
322
return None, self.open_branch()
324
def open_branch(self, name=None, unsupported=False,
325
ignore_fallbacks=False):
327
raise NotImplementedError('unsupported flag support not implemented yet.')
328
if self._next_open_branch_result is not None:
329
# See create_branch for details.
330
result = self._next_open_branch_result
331
self._next_open_branch_result = None
333
response = self._get_branch_reference()
334
if response[0] == 'ref':
335
# a branch reference, use the existing BranchReference logic.
336
format = BranchReferenceFormat()
337
return format.open(self, name=name, _found=True,
338
location=response[1], ignore_fallbacks=ignore_fallbacks)
339
branch_format_name = response[1]
340
if not branch_format_name:
341
branch_format_name = None
342
format = RemoteBranchFormat(network_name=branch_format_name)
343
return RemoteBranch(self, self.find_repository(), format=format,
344
setup_stacking=not ignore_fallbacks, name=name)
346
def _open_repo_v1(self, path):
347
verb = 'BzrDir.find_repository'
348
response = self._call(verb, path)
349
if response[0] != 'ok':
350
raise errors.UnexpectedSmartServerResponse(response)
351
# servers that only support the v1 method don't support external
354
repo = self._real_bzrdir.open_repository()
355
response = response + ('no', repo._format.network_name())
356
return response, repo
358
def _open_repo_v2(self, path):
359
verb = 'BzrDir.find_repositoryV2'
360
response = self._call(verb, path)
361
if response[0] != 'ok':
362
raise errors.UnexpectedSmartServerResponse(response)
364
repo = self._real_bzrdir.open_repository()
365
response = response + (repo._format.network_name(),)
366
return response, repo
368
def _open_repo_v3(self, path):
369
verb = 'BzrDir.find_repositoryV3'
370
medium = self._client._medium
371
if medium._is_remote_before((1, 13)):
372
raise errors.UnknownSmartMethod(verb)
374
response = self._call(verb, path)
375
except errors.UnknownSmartMethod:
376
medium._remember_remote_is_before((1, 13))
378
if response[0] != 'ok':
379
raise errors.UnexpectedSmartServerResponse(response)
380
return response, None
382
def open_repository(self):
383
path = self._path_for_remote_call(self._client)
385
for probe in [self._open_repo_v3, self._open_repo_v2,
388
response, real_repo = probe(path)
390
except errors.UnknownSmartMethod:
393
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
394
if response[0] != 'ok':
395
raise errors.UnexpectedSmartServerResponse(response)
396
if len(response) != 6:
397
raise SmartProtocolError('incorrect response length %s' % (response,))
398
if response[1] == '':
399
# repo is at this dir.
400
format = response_tuple_to_repo_format(response[2:])
401
# Used to support creating a real format instance when needed.
402
format._creating_bzrdir = self
403
remote_repo = RemoteRepository(self, format)
404
format._creating_repo = remote_repo
405
if real_repo is not None:
406
remote_repo._set_real_repository(real_repo)
409
raise errors.NoRepositoryPresent(self)
411
def has_workingtree(self):
412
if self._has_working_tree is None:
414
self._has_working_tree = self._real_bzrdir.has_workingtree()
415
return self._has_working_tree
417
def open_workingtree(self, recommend_upgrade=True):
418
if self.has_workingtree():
419
raise errors.NotLocalUrl(self.root_transport)
421
raise errors.NoWorkingTree(self.root_transport.base)
423
def _path_for_remote_call(self, client):
424
"""Return the path to be used for this bzrdir in a remote call."""
425
return client.remote_path_from_transport(self.root_transport)
427
def get_branch_transport(self, branch_format, name=None):
429
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
431
def get_repository_transport(self, repository_format):
433
return self._real_bzrdir.get_repository_transport(repository_format)
435
def get_workingtree_transport(self, workingtree_format):
437
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
439
def can_convert_format(self):
440
"""Upgrading of remote bzrdirs is not supported yet."""
443
def needs_format_conversion(self, format=None):
444
"""Upgrading of remote bzrdirs is not supported yet."""
446
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
447
% 'needs_format_conversion(format=None)')
450
def clone(self, url, revision_id=None, force_new_repo=False,
451
preserve_stacking=False):
453
return self._real_bzrdir.clone(url, revision_id=revision_id,
454
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
456
def _get_config(self):
457
return RemoteBzrDirConfig(self)
460
class RemoteRepositoryFormat(repository.RepositoryFormat):
461
"""Format for repositories accessed over a _SmartClient.
463
Instances of this repository are represented by RemoteRepository
466
The RemoteRepositoryFormat is parameterized during construction
467
to reflect the capabilities of the real, remote format. Specifically
468
the attributes rich_root_data and supports_tree_reference are set
469
on a per instance basis, and are not set (and should not be) at
472
:ivar _custom_format: If set, a specific concrete repository format that
473
will be used when initializing a repository with this
474
RemoteRepositoryFormat.
475
:ivar _creating_repo: If set, the repository object that this
476
RemoteRepositoryFormat was created for: it can be called into
477
to obtain data like the network name.
480
_matchingbzrdir = RemoteBzrDirFormat()
483
repository.RepositoryFormat.__init__(self)
484
self._custom_format = None
485
self._network_name = None
486
self._creating_bzrdir = None
487
self._supports_chks = None
488
self._supports_external_lookups = None
489
self._supports_tree_reference = None
490
self._rich_root_data = None
493
return "%s(_network_name=%r)" % (self.__class__.__name__,
497
def fast_deltas(self):
499
return self._custom_format.fast_deltas
502
def rich_root_data(self):
503
if self._rich_root_data is None:
505
self._rich_root_data = self._custom_format.rich_root_data
506
return self._rich_root_data
509
def supports_chks(self):
510
if self._supports_chks is None:
512
self._supports_chks = self._custom_format.supports_chks
513
return self._supports_chks
516
def supports_external_lookups(self):
517
if self._supports_external_lookups is None:
519
self._supports_external_lookups = \
520
self._custom_format.supports_external_lookups
521
return self._supports_external_lookups
524
def supports_tree_reference(self):
525
if self._supports_tree_reference is None:
527
self._supports_tree_reference = \
528
self._custom_format.supports_tree_reference
529
return self._supports_tree_reference
531
def _vfs_initialize(self, a_bzrdir, shared):
532
"""Helper for common code in initialize."""
533
if self._custom_format:
534
# Custom format requested
535
result = self._custom_format.initialize(a_bzrdir, shared=shared)
536
elif self._creating_bzrdir is not None:
537
# Use the format that the repository we were created to back
539
prior_repo = self._creating_bzrdir.open_repository()
540
prior_repo._ensure_real()
541
result = prior_repo._real_repository._format.initialize(
542
a_bzrdir, shared=shared)
544
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
545
# support remote initialization.
546
# We delegate to a real object at this point (as RemoteBzrDir
547
# delegate to the repository format which would lead to infinite
548
# recursion if we just called a_bzrdir.create_repository.
549
a_bzrdir._ensure_real()
550
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
551
if not isinstance(result, RemoteRepository):
552
return self.open(a_bzrdir)
556
def initialize(self, a_bzrdir, shared=False):
557
# Being asked to create on a non RemoteBzrDir:
558
if not isinstance(a_bzrdir, RemoteBzrDir):
559
return self._vfs_initialize(a_bzrdir, shared)
560
medium = a_bzrdir._client._medium
561
if medium._is_remote_before((1, 13)):
562
return self._vfs_initialize(a_bzrdir, shared)
563
# Creating on a remote bzr dir.
564
# 1) get the network name to use.
565
if self._custom_format:
566
network_name = self._custom_format.network_name()
567
elif self._network_name:
568
network_name = self._network_name
570
# Select the current bzrlib default and ask for that.
571
reference_bzrdir_format = bzrdir.format_registry.get('default')()
572
reference_format = reference_bzrdir_format.repository_format
573
network_name = reference_format.network_name()
574
# 2) try direct creation via RPC
575
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
576
verb = 'BzrDir.create_repository'
582
response = a_bzrdir._call(verb, path, network_name, shared_str)
583
except errors.UnknownSmartMethod:
584
# Fallback - use vfs methods
585
medium._remember_remote_is_before((1, 13))
586
return self._vfs_initialize(a_bzrdir, shared)
588
# Turn the response into a RemoteRepository object.
589
format = response_tuple_to_repo_format(response[1:])
590
# Used to support creating a real format instance when needed.
591
format._creating_bzrdir = a_bzrdir
592
remote_repo = RemoteRepository(a_bzrdir, format)
593
format._creating_repo = remote_repo
596
def open(self, a_bzrdir):
597
if not isinstance(a_bzrdir, RemoteBzrDir):
598
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
599
return a_bzrdir.open_repository()
601
def _ensure_real(self):
602
if self._custom_format is None:
603
self._custom_format = repository.network_format_registry.get(
607
def _fetch_order(self):
609
return self._custom_format._fetch_order
612
def _fetch_uses_deltas(self):
614
return self._custom_format._fetch_uses_deltas
617
def _fetch_reconcile(self):
619
return self._custom_format._fetch_reconcile
621
def get_format_description(self):
623
return 'Remote: ' + self._custom_format.get_format_description()
625
def __eq__(self, other):
626
return self.__class__ is other.__class__
628
def network_name(self):
629
if self._network_name:
630
return self._network_name
631
self._creating_repo._ensure_real()
632
return self._creating_repo._real_repository._format.network_name()
635
def pack_compresses(self):
637
return self._custom_format.pack_compresses
640
def _serializer(self):
642
return self._custom_format._serializer
645
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
646
"""Repository accessed over rpc.
648
For the moment most operations are performed using local transport-backed
652
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
653
"""Create a RemoteRepository instance.
655
:param remote_bzrdir: The bzrdir hosting this repository.
656
:param format: The RemoteFormat object to use.
657
:param real_repository: If not None, a local implementation of the
658
repository logic for the repository, usually accessing the data
660
:param _client: Private testing parameter - override the smart client
661
to be used by the repository.
664
self._real_repository = real_repository
666
self._real_repository = None
667
self.bzrdir = remote_bzrdir
669
self._client = remote_bzrdir._client
671
self._client = _client
672
self._format = format
673
self._lock_mode = None
674
self._lock_token = None
676
self._leave_lock = False
677
# Cache of revision parents; misses are cached during read locks, and
678
# write locks when no _real_repository has been set.
679
self._unstacked_provider = graph.CachingParentsProvider(
680
get_parent_map=self._get_parent_map_rpc)
681
self._unstacked_provider.disable_cache()
683
# These depend on the actual remote format, so force them off for
684
# maximum compatibility. XXX: In future these should depend on the
685
# remote repository instance, but this is irrelevant until we perform
686
# reconcile via an RPC call.
687
self._reconcile_does_inventory_gc = False
688
self._reconcile_fixes_text_parents = False
689
self._reconcile_backsup_inventory = False
690
self.base = self.bzrdir.transport.base
691
# Additional places to query for data.
692
self._fallback_repositories = []
695
return "%s(%s)" % (self.__class__.__name__, self.base)
699
def abort_write_group(self, suppress_errors=False):
700
"""Complete a write group on the decorated repository.
702
Smart methods perform operations in a single step so this API
703
is not really applicable except as a compatibility thunk
704
for older plugins that don't use e.g. the CommitBuilder
707
:param suppress_errors: see Repository.abort_write_group.
710
return self._real_repository.abort_write_group(
711
suppress_errors=suppress_errors)
715
"""Decorate the real repository for now.
717
In the long term a full blown network facility is needed to avoid
718
creating a real repository object locally.
721
return self._real_repository.chk_bytes
723
def commit_write_group(self):
724
"""Complete a write group on the decorated repository.
726
Smart methods perform operations in a single step so this API
727
is not really applicable except as a compatibility thunk
728
for older plugins that don't use e.g. the CommitBuilder
732
return self._real_repository.commit_write_group()
734
def resume_write_group(self, tokens):
736
return self._real_repository.resume_write_group(tokens)
738
def suspend_write_group(self):
740
return self._real_repository.suspend_write_group()
742
def get_missing_parent_inventories(self, check_for_missing_texts=True):
744
return self._real_repository.get_missing_parent_inventories(
745
check_for_missing_texts=check_for_missing_texts)
747
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
749
return self._real_repository.get_rev_id_for_revno(
752
def get_rev_id_for_revno(self, revno, known_pair):
753
"""See Repository.get_rev_id_for_revno."""
754
path = self.bzrdir._path_for_remote_call(self._client)
756
if self._client._medium._is_remote_before((1, 17)):
757
return self._get_rev_id_for_revno_vfs(revno, known_pair)
758
response = self._call(
759
'Repository.get_rev_id_for_revno', path, revno, known_pair)
760
except errors.UnknownSmartMethod:
761
self._client._medium._remember_remote_is_before((1, 17))
762
return self._get_rev_id_for_revno_vfs(revno, known_pair)
763
if response[0] == 'ok':
764
return True, response[1]
765
elif response[0] == 'history-incomplete':
766
known_pair = response[1:3]
767
for fallback in self._fallback_repositories:
768
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
773
# Not found in any fallbacks
774
return False, known_pair
776
raise errors.UnexpectedSmartServerResponse(response)
778
def _ensure_real(self):
779
"""Ensure that there is a _real_repository set.
781
Used before calls to self._real_repository.
783
Note that _ensure_real causes many roundtrips to the server which are
784
not desirable, and prevents the use of smart one-roundtrip RPC's to
785
perform complex operations (such as accessing parent data, streaming
786
revisions etc). Adding calls to _ensure_real should only be done when
787
bringing up new functionality, adding fallbacks for smart methods that
788
require a fallback path, and never to replace an existing smart method
789
invocation. If in doubt chat to the bzr network team.
791
if self._real_repository is None:
792
if 'hpssvfs' in debug.debug_flags:
794
warning('VFS Repository access triggered\n%s',
795
''.join(traceback.format_stack()))
796
self._unstacked_provider.missing_keys.clear()
797
self.bzrdir._ensure_real()
798
self._set_real_repository(
799
self.bzrdir._real_bzrdir.open_repository())
801
def _translate_error(self, err, **context):
802
self.bzrdir._translate_error(err, repository=self, **context)
804
def find_text_key_references(self):
805
"""Find the text key references within the repository.
807
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
808
revision_ids. Each altered file-ids has the exact revision_ids that
809
altered it listed explicitly.
810
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
811
to whether they were referred to by the inventory of the
812
revision_id that they contain. The inventory texts from all present
813
revision ids are assessed to generate this report.
816
return self._real_repository.find_text_key_references()
818
def _generate_text_key_index(self):
819
"""Generate a new text key index for the repository.
821
This is an expensive function that will take considerable time to run.
823
:return: A dict mapping (file_id, revision_id) tuples to a list of
824
parents, also (file_id, revision_id) tuples.
827
return self._real_repository._generate_text_key_index()
829
def _get_revision_graph(self, revision_id):
830
"""Private method for using with old (< 1.2) servers to fallback."""
831
if revision_id is None:
833
elif revision.is_null(revision_id):
836
path = self.bzrdir._path_for_remote_call(self._client)
837
response = self._call_expecting_body(
838
'Repository.get_revision_graph', path, revision_id)
839
response_tuple, response_handler = response
840
if response_tuple[0] != 'ok':
841
raise errors.UnexpectedSmartServerResponse(response_tuple)
842
coded = response_handler.read_body_bytes()
844
# no revisions in this repository!
846
lines = coded.split('\n')
849
d = tuple(line.split())
850
revision_graph[d[0]] = d[1:]
852
return revision_graph
855
"""See Repository._get_sink()."""
856
return RemoteStreamSink(self)
858
def _get_source(self, to_format):
859
"""Return a source for streaming from this repository."""
860
return RemoteStreamSource(self, to_format)
863
def has_revision(self, revision_id):
864
"""True if this repository has a copy of the revision."""
865
# Copy of bzrlib.repository.Repository.has_revision
866
return revision_id in self.has_revisions((revision_id,))
869
def has_revisions(self, revision_ids):
870
"""Probe to find out the presence of multiple revisions.
872
:param revision_ids: An iterable of revision_ids.
873
:return: A set of the revision_ids that were present.
875
# Copy of bzrlib.repository.Repository.has_revisions
876
parent_map = self.get_parent_map(revision_ids)
877
result = set(parent_map)
878
if _mod_revision.NULL_REVISION in revision_ids:
879
result.add(_mod_revision.NULL_REVISION)
882
def _has_same_fallbacks(self, other_repo):
883
"""Returns true if the repositories have the same fallbacks."""
884
# XXX: copied from Repository; it should be unified into a base class
885
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
886
my_fb = self._fallback_repositories
887
other_fb = other_repo._fallback_repositories
888
if len(my_fb) != len(other_fb):
890
for f, g in zip(my_fb, other_fb):
891
if not f.has_same_location(g):
895
def has_same_location(self, other):
896
# TODO: Move to RepositoryBase and unify with the regular Repository
897
# one; unfortunately the tests rely on slightly different behaviour at
898
# present -- mbp 20090710
899
return (self.__class__ is other.__class__ and
900
self.bzrdir.transport.base == other.bzrdir.transport.base)
902
def get_graph(self, other_repository=None):
903
"""Return the graph for this repository format"""
904
parents_provider = self._make_parents_provider(other_repository)
905
return graph.Graph(parents_provider)
908
def get_known_graph_ancestry(self, revision_ids):
909
"""Return the known graph for a set of revision ids and their ancestors.
911
st = static_tuple.StaticTuple
912
revision_keys = [st(r_id).intern() for r_id in revision_ids]
913
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
914
return graph.GraphThunkIdsToKeys(known_graph)
916
def gather_stats(self, revid=None, committers=None):
917
"""See Repository.gather_stats()."""
918
path = self.bzrdir._path_for_remote_call(self._client)
919
# revid can be None to indicate no revisions, not just NULL_REVISION
920
if revid is None or revision.is_null(revid):
924
if committers is None or not committers:
925
fmt_committers = 'no'
927
fmt_committers = 'yes'
928
response_tuple, response_handler = self._call_expecting_body(
929
'Repository.gather_stats', path, fmt_revid, fmt_committers)
930
if response_tuple[0] != 'ok':
931
raise errors.UnexpectedSmartServerResponse(response_tuple)
933
body = response_handler.read_body_bytes()
935
for line in body.split('\n'):
938
key, val_text = line.split(':')
939
if key in ('revisions', 'size', 'committers'):
940
result[key] = int(val_text)
941
elif key in ('firstrev', 'latestrev'):
942
values = val_text.split(' ')[1:]
943
result[key] = (float(values[0]), long(values[1]))
947
def find_branches(self, using=False):
948
"""See Repository.find_branches()."""
949
# should be an API call to the server.
951
return self._real_repository.find_branches(using=using)
953
def get_physical_lock_status(self):
954
"""See Repository.get_physical_lock_status()."""
955
# should be an API call to the server.
957
return self._real_repository.get_physical_lock_status()
959
def is_in_write_group(self):
960
"""Return True if there is an open write group.
962
write groups are only applicable locally for the smart server..
964
if self._real_repository:
965
return self._real_repository.is_in_write_group()
968
return self._lock_count >= 1
971
"""See Repository.is_shared()."""
972
path = self.bzrdir._path_for_remote_call(self._client)
973
response = self._call('Repository.is_shared', path)
974
if response[0] not in ('yes', 'no'):
975
raise SmartProtocolError('unexpected response code %s' % (response,))
976
return response[0] == 'yes'
978
def is_write_locked(self):
979
return self._lock_mode == 'w'
981
def _warn_if_deprecated(self, branch=None):
982
# If we have a real repository, the check will be done there, if we
983
# don't the check will be done remotely.
987
# wrong eventually - want a local lock cache context
988
if not self._lock_mode:
990
self._lock_mode = 'r'
992
self._unstacked_provider.enable_cache(cache_misses=True)
993
if self._real_repository is not None:
994
self._real_repository.lock_read()
995
for repo in self._fallback_repositories:
998
self._lock_count += 1
1000
def _remote_lock_write(self, token):
1001
path = self.bzrdir._path_for_remote_call(self._client)
1004
err_context = {'token': token}
1005
response = self._call('Repository.lock_write', path, token,
1007
if response[0] == 'ok':
1008
ok, token = response
1011
raise errors.UnexpectedSmartServerResponse(response)
1013
def lock_write(self, token=None, _skip_rpc=False):
1014
if not self._lock_mode:
1015
self._note_lock('w')
1017
if self._lock_token is not None:
1018
if token != self._lock_token:
1019
raise errors.TokenMismatch(token, self._lock_token)
1020
self._lock_token = token
1022
self._lock_token = self._remote_lock_write(token)
1023
# if self._lock_token is None, then this is something like packs or
1024
# svn where we don't get to lock the repo, or a weave style repository
1025
# where we cannot lock it over the wire and attempts to do so will
1027
if self._real_repository is not None:
1028
self._real_repository.lock_write(token=self._lock_token)
1029
if token is not None:
1030
self._leave_lock = True
1032
self._leave_lock = False
1033
self._lock_mode = 'w'
1034
self._lock_count = 1
1035
cache_misses = self._real_repository is None
1036
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1037
for repo in self._fallback_repositories:
1038
# Writes don't affect fallback repos
1040
elif self._lock_mode == 'r':
1041
raise errors.ReadOnlyError(self)
1043
self._lock_count += 1
1044
return self._lock_token or None
1046
def leave_lock_in_place(self):
1047
if not self._lock_token:
1048
raise NotImplementedError(self.leave_lock_in_place)
1049
self._leave_lock = True
1051
def dont_leave_lock_in_place(self):
1052
if not self._lock_token:
1053
raise NotImplementedError(self.dont_leave_lock_in_place)
1054
self._leave_lock = False
1056
def _set_real_repository(self, repository):
1057
"""Set the _real_repository for this repository.
1059
:param repository: The repository to fallback to for non-hpss
1060
implemented operations.
1062
if self._real_repository is not None:
1063
# Replacing an already set real repository.
1064
# We cannot do this [currently] if the repository is locked -
1065
# synchronised state might be lost.
1066
if self.is_locked():
1067
raise AssertionError('_real_repository is already set')
1068
if isinstance(repository, RemoteRepository):
1069
raise AssertionError()
1070
self._real_repository = repository
1071
# three code paths happen here:
1072
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1073
# up stacking. In this case self._fallback_repositories is [], and the
1074
# real repo is already setup. Preserve the real repo and
1075
# RemoteRepository.add_fallback_repository will avoid adding
1077
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1078
# ensure_real is triggered from a branch, the real repository to
1079
# set already has a matching list with separate instances, but
1080
# as they are also RemoteRepositories we don't worry about making the
1081
# lists be identical.
1082
# 3) new servers, RemoteRepository.ensure_real is triggered before
1083
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1084
# and need to populate it.
1085
if (self._fallback_repositories and
1086
len(self._real_repository._fallback_repositories) !=
1087
len(self._fallback_repositories)):
1088
if len(self._real_repository._fallback_repositories):
1089
raise AssertionError(
1090
"cannot cleanly remove existing _fallback_repositories")
1091
for fb in self._fallback_repositories:
1092
self._real_repository.add_fallback_repository(fb)
1093
if self._lock_mode == 'w':
1094
# if we are already locked, the real repository must be able to
1095
# acquire the lock with our token.
1096
self._real_repository.lock_write(self._lock_token)
1097
elif self._lock_mode == 'r':
1098
self._real_repository.lock_read()
1100
def start_write_group(self):
1101
"""Start a write group on the decorated repository.
1103
Smart methods perform operations in a single step so this API
1104
is not really applicable except as a compatibility thunk
1105
for older plugins that don't use e.g. the CommitBuilder
1109
return self._real_repository.start_write_group()
1111
def _unlock(self, token):
1112
path = self.bzrdir._path_for_remote_call(self._client)
1114
# with no token the remote repository is not persistently locked.
1116
err_context = {'token': token}
1117
response = self._call('Repository.unlock', path, token,
1119
if response == ('ok',):
1122
raise errors.UnexpectedSmartServerResponse(response)
1124
@only_raises(errors.LockNotHeld, errors.LockBroken)
1126
if not self._lock_count:
1127
return lock.cant_unlock_not_held(self)
1128
self._lock_count -= 1
1129
if self._lock_count > 0:
1131
self._unstacked_provider.disable_cache()
1132
old_mode = self._lock_mode
1133
self._lock_mode = None
1135
# The real repository is responsible at present for raising an
1136
# exception if it's in an unfinished write group. However, it
1137
# normally will *not* actually remove the lock from disk - that's
1138
# done by the server on receiving the Repository.unlock call.
1139
# This is just to let the _real_repository stay up to date.
1140
if self._real_repository is not None:
1141
self._real_repository.unlock()
1143
# The rpc-level lock should be released even if there was a
1144
# problem releasing the vfs-based lock.
1146
# Only write-locked repositories need to make a remote method
1147
# call to perform the unlock.
1148
old_token = self._lock_token
1149
self._lock_token = None
1150
if not self._leave_lock:
1151
self._unlock(old_token)
1152
# Fallbacks are always 'lock_read()' so we don't pay attention to
1154
for repo in self._fallback_repositories:
1157
def break_lock(self):
1158
# should hand off to the network
1160
return self._real_repository.break_lock()
1162
def _get_tarball(self, compression):
1163
"""Return a TemporaryFile containing a repository tarball.
1165
Returns None if the server does not support sending tarballs.
1168
path = self.bzrdir._path_for_remote_call(self._client)
1170
response, protocol = self._call_expecting_body(
1171
'Repository.tarball', path, compression)
1172
except errors.UnknownSmartMethod:
1173
protocol.cancel_read_body()
1175
if response[0] == 'ok':
1176
# Extract the tarball and return it
1177
t = tempfile.NamedTemporaryFile()
1178
# TODO: rpc layer should read directly into it...
1179
t.write(protocol.read_body_bytes())
1182
raise errors.UnexpectedSmartServerResponse(response)
1184
def sprout(self, to_bzrdir, revision_id=None):
1185
# TODO: Option to control what format is created?
1187
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1189
dest_repo.fetch(self, revision_id=revision_id)
1192
### These methods are just thin shims to the VFS object for now.
1194
def revision_tree(self, revision_id):
1196
return self._real_repository.revision_tree(revision_id)
1198
def get_serializer_format(self):
1200
return self._real_repository.get_serializer_format()
1202
def get_commit_builder(self, branch, parents, config, timestamp=None,
1203
timezone=None, committer=None, revprops=None,
1205
# FIXME: It ought to be possible to call this without immediately
1206
# triggering _ensure_real. For now it's the easiest thing to do.
1208
real_repo = self._real_repository
1209
builder = real_repo.get_commit_builder(branch, parents,
1210
config, timestamp=timestamp, timezone=timezone,
1211
committer=committer, revprops=revprops, revision_id=revision_id)
1214
def add_fallback_repository(self, repository):
1215
"""Add a repository to use for looking up data not held locally.
1217
:param repository: A repository.
1219
if not self._format.supports_external_lookups:
1220
raise errors.UnstackableRepositoryFormat(
1221
self._format.network_name(), self.base)
1222
# We need to accumulate additional repositories here, to pass them in
1225
if self.is_locked():
1226
# We will call fallback.unlock() when we transition to the unlocked
1227
# state, so always add a lock here. If a caller passes us a locked
1228
# repository, they are responsible for unlocking it later.
1229
repository.lock_read()
1230
self._fallback_repositories.append(repository)
1231
# If self._real_repository was parameterised already (e.g. because a
1232
# _real_branch had its get_stacked_on_url method called), then the
1233
# repository to be added may already be in the _real_repositories list.
1234
if self._real_repository is not None:
1235
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1236
self._real_repository._fallback_repositories]
1237
if repository.bzrdir.root_transport.base not in fallback_locations:
1238
self._real_repository.add_fallback_repository(repository)
1240
def add_inventory(self, revid, inv, parents):
1242
return self._real_repository.add_inventory(revid, inv, parents)
1244
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1245
parents, basis_inv=None, propagate_caches=False):
1247
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1248
delta, new_revision_id, parents, basis_inv=basis_inv,
1249
propagate_caches=propagate_caches)
1251
def add_revision(self, rev_id, rev, inv=None, config=None):
1253
return self._real_repository.add_revision(
1254
rev_id, rev, inv=inv, config=config)
1257
def get_inventory(self, revision_id):
1259
return self._real_repository.get_inventory(revision_id)
1261
def iter_inventories(self, revision_ids, ordering=None):
1263
return self._real_repository.iter_inventories(revision_ids, ordering)
1266
def get_revision(self, revision_id):
1268
return self._real_repository.get_revision(revision_id)
1270
def get_transaction(self):
1272
return self._real_repository.get_transaction()
1275
def clone(self, a_bzrdir, revision_id=None):
1277
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1279
def make_working_trees(self):
1280
"""See Repository.make_working_trees"""
1282
return self._real_repository.make_working_trees()
1284
def refresh_data(self):
1285
"""Re-read any data needed to to synchronise with disk.
1287
This method is intended to be called after another repository instance
1288
(such as one used by a smart server) has inserted data into the
1289
repository. It may not be called during a write group, but may be
1290
called at any other time.
1292
if self.is_in_write_group():
1293
raise errors.InternalBzrError(
1294
"May not refresh_data while in a write group.")
1295
if self._real_repository is not None:
1296
self._real_repository.refresh_data()
1298
def revision_ids_to_search_result(self, result_set):
1299
"""Convert a set of revision ids to a graph SearchResult."""
1300
result_parents = set()
1301
for parents in self.get_graph().get_parent_map(
1302
result_set).itervalues():
1303
result_parents.update(parents)
1304
included_keys = result_set.intersection(result_parents)
1305
start_keys = result_set.difference(included_keys)
1306
exclude_keys = result_parents.difference(result_set)
1307
result = graph.SearchResult(start_keys, exclude_keys,
1308
len(result_set), result_set)
1312
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1313
"""Return the revision ids that other has that this does not.
1315
These are returned in topological order.
1317
revision_id: only return revision ids included by revision_id.
1319
return repository.InterRepository.get(
1320
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1322
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1324
# No base implementation to use as RemoteRepository is not a subclass
1325
# of Repository; so this is a copy of Repository.fetch().
1326
if fetch_spec is not None and revision_id is not None:
1327
raise AssertionError(
1328
"fetch_spec and revision_id are mutually exclusive.")
1329
if self.is_in_write_group():
1330
raise errors.InternalBzrError(
1331
"May not fetch while in a write group.")
1332
# fast path same-url fetch operations
1333
if (self.has_same_location(source)
1334
and fetch_spec is None
1335
and self._has_same_fallbacks(source)):
1336
# check that last_revision is in 'from' and then return a
1338
if (revision_id is not None and
1339
not revision.is_null(revision_id)):
1340
self.get_revision(revision_id)
1342
# if there is no specific appropriate InterRepository, this will get
1343
# the InterRepository base class, which raises an
1344
# IncompatibleRepositories when asked to fetch.
1345
inter = repository.InterRepository.get(source, self)
1346
return inter.fetch(revision_id=revision_id, pb=pb,
1347
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1349
def create_bundle(self, target, base, fileobj, format=None):
1351
self._real_repository.create_bundle(target, base, fileobj, format)
1354
def get_ancestry(self, revision_id, topo_sorted=True):
1356
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1358
def fileids_altered_by_revision_ids(self, revision_ids):
1360
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1362
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1364
return self._real_repository._get_versioned_file_checker(
1365
revisions, revision_versions_cache)
1367
def iter_files_bytes(self, desired_files):
1368
"""See Repository.iter_file_bytes.
1371
return self._real_repository.iter_files_bytes(desired_files)
1373
def get_parent_map(self, revision_ids):
1374
"""See bzrlib.Graph.get_parent_map()."""
1375
return self._make_parents_provider().get_parent_map(revision_ids)
1377
def _get_parent_map_rpc(self, keys):
1378
"""Helper for get_parent_map that performs the RPC."""
1379
medium = self._client._medium
1380
if medium._is_remote_before((1, 2)):
1381
# We already found out that the server can't understand
1382
# Repository.get_parent_map requests, so just fetch the whole
1385
# Note that this reads the whole graph, when only some keys are
1386
# wanted. On this old server there's no way (?) to get them all
1387
# in one go, and the user probably will have seen a warning about
1388
# the server being old anyhow.
1389
rg = self._get_revision_graph(None)
1390
# There is an API discrepancy between get_parent_map and
1391
# get_revision_graph. Specifically, a "key:()" pair in
1392
# get_revision_graph just means a node has no parents. For
1393
# "get_parent_map" it means the node is a ghost. So fix up the
1394
# graph to correct this.
1395
# https://bugs.launchpad.net/bzr/+bug/214894
1396
# There is one other "bug" which is that ghosts in
1397
# get_revision_graph() are not returned at all. But we won't worry
1398
# about that for now.
1399
for node_id, parent_ids in rg.iteritems():
1400
if parent_ids == ():
1401
rg[node_id] = (NULL_REVISION,)
1402
rg[NULL_REVISION] = ()
1407
raise ValueError('get_parent_map(None) is not valid')
1408
if NULL_REVISION in keys:
1409
keys.discard(NULL_REVISION)
1410
found_parents = {NULL_REVISION:()}
1412
return found_parents
1415
# TODO(Needs analysis): We could assume that the keys being requested
1416
# from get_parent_map are in a breadth first search, so typically they
1417
# will all be depth N from some common parent, and we don't have to
1418
# have the server iterate from the root parent, but rather from the
1419
# keys we're searching; and just tell the server the keyspace we
1420
# already have; but this may be more traffic again.
1422
# Transform self._parents_map into a search request recipe.
1423
# TODO: Manage this incrementally to avoid covering the same path
1424
# repeatedly. (The server will have to on each request, but the less
1425
# work done the better).
1427
# Negative caching notes:
1428
# new server sends missing when a request including the revid
1429
# 'include-missing:' is present in the request.
1430
# missing keys are serialised as missing:X, and we then call
1431
# provider.note_missing(X) for-all X
1432
parents_map = self._unstacked_provider.get_cached_map()
1433
if parents_map is None:
1434
# Repository is not locked, so there's no cache.
1436
# start_set is all the keys in the cache
1437
start_set = set(parents_map)
1438
# result set is all the references to keys in the cache
1439
result_parents = set()
1440
for parents in parents_map.itervalues():
1441
result_parents.update(parents)
1442
stop_keys = result_parents.difference(start_set)
1443
# We don't need to send ghosts back to the server as a position to
1445
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1446
key_count = len(parents_map)
1447
if (NULL_REVISION in result_parents
1448
and NULL_REVISION in self._unstacked_provider.missing_keys):
1449
# If we pruned NULL_REVISION from the stop_keys because it's also
1450
# in our cache of "missing" keys we need to increment our key count
1451
# by 1, because the reconsitituted SearchResult on the server will
1452
# still consider NULL_REVISION to be an included key.
1454
included_keys = start_set.intersection(result_parents)
1455
start_set.difference_update(included_keys)
1456
recipe = ('manual', start_set, stop_keys, key_count)
1457
body = self._serialise_search_recipe(recipe)
1458
path = self.bzrdir._path_for_remote_call(self._client)
1460
if type(key) is not str:
1462
"key %r not a plain string" % (key,))
1463
verb = 'Repository.get_parent_map'
1464
args = (path, 'include-missing:') + tuple(keys)
1466
response = self._call_with_body_bytes_expecting_body(
1468
except errors.UnknownSmartMethod:
1469
# Server does not support this method, so get the whole graph.
1470
# Worse, we have to force a disconnection, because the server now
1471
# doesn't realise it has a body on the wire to consume, so the
1472
# only way to recover is to abandon the connection.
1474
'Server is too old for fast get_parent_map, reconnecting. '
1475
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1477
# To avoid having to disconnect repeatedly, we keep track of the
1478
# fact the server doesn't understand remote methods added in 1.2.
1479
medium._remember_remote_is_before((1, 2))
1480
# Recurse just once and we should use the fallback code.
1481
return self._get_parent_map_rpc(keys)
1482
response_tuple, response_handler = response
1483
if response_tuple[0] not in ['ok']:
1484
response_handler.cancel_read_body()
1485
raise errors.UnexpectedSmartServerResponse(response_tuple)
1486
if response_tuple[0] == 'ok':
1487
coded = bz2.decompress(response_handler.read_body_bytes())
1489
# no revisions found
1491
lines = coded.split('\n')
1494
d = tuple(line.split())
1496
revision_graph[d[0]] = d[1:]
1499
if d[0].startswith('missing:'):
1501
self._unstacked_provider.note_missing_key(revid)
1503
# no parents - so give the Graph result
1505
revision_graph[d[0]] = (NULL_REVISION,)
1506
return revision_graph
1509
def get_signature_text(self, revision_id):
1511
return self._real_repository.get_signature_text(revision_id)
1514
def _get_inventory_xml(self, revision_id):
1516
return self._real_repository._get_inventory_xml(revision_id)
1518
def reconcile(self, other=None, thorough=False):
1520
return self._real_repository.reconcile(other=other, thorough=thorough)
1522
def all_revision_ids(self):
1524
return self._real_repository.all_revision_ids()
1527
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1529
return self._real_repository.get_deltas_for_revisions(revisions,
1530
specific_fileids=specific_fileids)
1533
def get_revision_delta(self, revision_id, specific_fileids=None):
1535
return self._real_repository.get_revision_delta(revision_id,
1536
specific_fileids=specific_fileids)
1539
def revision_trees(self, revision_ids):
1541
return self._real_repository.revision_trees(revision_ids)
1544
def get_revision_reconcile(self, revision_id):
1546
return self._real_repository.get_revision_reconcile(revision_id)
1549
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1551
return self._real_repository.check(revision_ids=revision_ids,
1552
callback_refs=callback_refs, check_repo=check_repo)
1554
def copy_content_into(self, destination, revision_id=None):
1556
return self._real_repository.copy_content_into(
1557
destination, revision_id=revision_id)
1559
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1560
# get a tarball of the remote repository, and copy from that into the
1562
from bzrlib import osutils
1564
# TODO: Maybe a progress bar while streaming the tarball?
1565
note("Copying repository content as tarball...")
1566
tar_file = self._get_tarball('bz2')
1567
if tar_file is None:
1569
destination = to_bzrdir.create_repository()
1571
tar = tarfile.open('repository', fileobj=tar_file,
1573
tmpdir = osutils.mkdtemp()
1575
_extract_tar(tar, tmpdir)
1576
tmp_bzrdir = BzrDir.open(tmpdir)
1577
tmp_repo = tmp_bzrdir.open_repository()
1578
tmp_repo.copy_content_into(destination, revision_id)
1580
osutils.rmtree(tmpdir)
1584
# TODO: Suggestion from john: using external tar is much faster than
1585
# python's tarfile library, but it may not work on windows.
1588
def inventories(self):
1589
"""Decorate the real repository for now.
1591
In the long term a full blown network facility is needed to
1592
avoid creating a real repository object locally.
1595
return self._real_repository.inventories
1598
def pack(self, hint=None):
1599
"""Compress the data within the repository.
1601
This is not currently implemented within the smart server.
1604
return self._real_repository.pack(hint=hint)
1607
def revisions(self):
1608
"""Decorate the real repository for now.
1610
In the short term this should become a real object to intercept graph
1613
In the long term a full blown network facility is needed.
1616
return self._real_repository.revisions
1618
def set_make_working_trees(self, new_value):
1620
new_value_str = "True"
1622
new_value_str = "False"
1623
path = self.bzrdir._path_for_remote_call(self._client)
1625
response = self._call(
1626
'Repository.set_make_working_trees', path, new_value_str)
1627
except errors.UnknownSmartMethod:
1629
self._real_repository.set_make_working_trees(new_value)
1631
if response[0] != 'ok':
1632
raise errors.UnexpectedSmartServerResponse(response)
1635
def signatures(self):
1636
"""Decorate the real repository for now.
1638
In the long term a full blown network facility is needed to avoid
1639
creating a real repository object locally.
1642
return self._real_repository.signatures
1645
def sign_revision(self, revision_id, gpg_strategy):
1647
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1651
"""Decorate the real repository for now.
1653
In the long term a full blown network facility is needed to avoid
1654
creating a real repository object locally.
1657
return self._real_repository.texts
1660
def get_revisions(self, revision_ids):
1662
return self._real_repository.get_revisions(revision_ids)
1664
def supports_rich_root(self):
1665
return self._format.rich_root_data
1667
def iter_reverse_revision_history(self, revision_id):
1669
return self._real_repository.iter_reverse_revision_history(revision_id)
1672
def _serializer(self):
1673
return self._format._serializer
1675
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1677
return self._real_repository.store_revision_signature(
1678
gpg_strategy, plaintext, revision_id)
1680
def add_signature_text(self, revision_id, signature):
1682
return self._real_repository.add_signature_text(revision_id, signature)
1684
def has_signature_for_revision_id(self, revision_id):
1686
return self._real_repository.has_signature_for_revision_id(revision_id)
1688
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1690
return self._real_repository.item_keys_introduced_by(revision_ids,
1691
_files_pb=_files_pb)
1693
def revision_graph_can_have_wrong_parents(self):
1694
# The answer depends on the remote repo format.
1696
return self._real_repository.revision_graph_can_have_wrong_parents()
1698
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1700
return self._real_repository._find_inconsistent_revision_parents(
1703
def _check_for_inconsistent_revision_parents(self):
1705
return self._real_repository._check_for_inconsistent_revision_parents()
1707
def _make_parents_provider(self, other=None):
1708
providers = [self._unstacked_provider]
1709
if other is not None:
1710
providers.insert(0, other)
1711
providers.extend(r._make_parents_provider() for r in
1712
self._fallback_repositories)
1713
return graph.StackedParentsProvider(providers)
1715
def _serialise_search_recipe(self, recipe):
1716
"""Serialise a graph search recipe.
1718
:param recipe: A search recipe (start, stop, count).
1719
:return: Serialised bytes.
1721
start_keys = ' '.join(recipe[1])
1722
stop_keys = ' '.join(recipe[2])
1723
count = str(recipe[3])
1724
return '\n'.join((start_keys, stop_keys, count))
1726
def _serialise_search_result(self, search_result):
1727
if isinstance(search_result, graph.PendingAncestryResult):
1728
parts = ['ancestry-of']
1729
parts.extend(search_result.heads)
1731
recipe = search_result.get_recipe()
1732
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1733
return '\n'.join(parts)
1736
path = self.bzrdir._path_for_remote_call(self._client)
1738
response = self._call('PackRepository.autopack', path)
1739
except errors.UnknownSmartMethod:
1741
self._real_repository._pack_collection.autopack()
1744
if response[0] != 'ok':
1745
raise errors.UnexpectedSmartServerResponse(response)
1748
class RemoteStreamSink(repository.StreamSink):
1750
def _insert_real(self, stream, src_format, resume_tokens):
1751
self.target_repo._ensure_real()
1752
sink = self.target_repo._real_repository._get_sink()
1753
result = sink.insert_stream(stream, src_format, resume_tokens)
1755
self.target_repo.autopack()
1758
def insert_stream(self, stream, src_format, resume_tokens):
1759
target = self.target_repo
1760
target._unstacked_provider.missing_keys.clear()
1761
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1762
if target._lock_token:
1763
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1764
lock_args = (target._lock_token or '',)
1766
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1768
client = target._client
1769
medium = client._medium
1770
path = target.bzrdir._path_for_remote_call(client)
1771
# Probe for the verb to use with an empty stream before sending the
1772
# real stream to it. We do this both to avoid the risk of sending a
1773
# large request that is then rejected, and because we don't want to
1774
# implement a way to buffer, rewind, or restart the stream.
1776
for verb, required_version in candidate_calls:
1777
if medium._is_remote_before(required_version):
1780
# We've already done the probing (and set _is_remote_before) on
1781
# a previous insert.
1784
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1786
response = client.call_with_body_stream(
1787
(verb, path, '') + lock_args, byte_stream)
1788
except errors.UnknownSmartMethod:
1789
medium._remember_remote_is_before(required_version)
1795
return self._insert_real(stream, src_format, resume_tokens)
1796
self._last_inv_record = None
1797
self._last_substream = None
1798
if required_version < (1, 19):
1799
# Remote side doesn't support inventory deltas. Wrap the stream to
1800
# make sure we don't send any. If the stream contains inventory
1801
# deltas we'll interrupt the smart insert_stream request and
1803
stream = self._stop_stream_if_inventory_delta(stream)
1804
byte_stream = smart_repo._stream_to_byte_stream(
1806
resume_tokens = ' '.join(resume_tokens)
1807
response = client.call_with_body_stream(
1808
(verb, path, resume_tokens) + lock_args, byte_stream)
1809
if response[0][0] not in ('ok', 'missing-basis'):
1810
raise errors.UnexpectedSmartServerResponse(response)
1811
if self._last_substream is not None:
1812
# The stream included an inventory-delta record, but the remote
1813
# side isn't new enough to support them. So we need to send the
1814
# rest of the stream via VFS.
1815
self.target_repo.refresh_data()
1816
return self._resume_stream_with_vfs(response, src_format)
1817
if response[0][0] == 'missing-basis':
1818
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1819
resume_tokens = tokens
1820
return resume_tokens, set(missing_keys)
1822
self.target_repo.refresh_data()
1825
def _resume_stream_with_vfs(self, response, src_format):
1826
"""Resume sending a stream via VFS, first resending the record and
1827
substream that couldn't be sent via an insert_stream verb.
1829
if response[0][0] == 'missing-basis':
1830
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1831
# Ignore missing_keys, we haven't finished inserting yet
1834
def resume_substream():
1835
# Yield the substream that was interrupted.
1836
for record in self._last_substream:
1838
self._last_substream = None
1839
def resume_stream():
1840
# Finish sending the interrupted substream
1841
yield ('inventory-deltas', resume_substream())
1842
# Then simply continue sending the rest of the stream.
1843
for substream_kind, substream in self._last_stream:
1844
yield substream_kind, substream
1845
return self._insert_real(resume_stream(), src_format, tokens)
1847
def _stop_stream_if_inventory_delta(self, stream):
1848
"""Normally this just lets the original stream pass-through unchanged.
1850
However if any 'inventory-deltas' substream occurs it will stop
1851
streaming, and store the interrupted substream and stream in
1852
self._last_substream and self._last_stream so that the stream can be
1853
resumed by _resume_stream_with_vfs.
1856
stream_iter = iter(stream)
1857
for substream_kind, substream in stream_iter:
1858
if substream_kind == 'inventory-deltas':
1859
self._last_substream = substream
1860
self._last_stream = stream_iter
1863
yield substream_kind, substream
1866
class RemoteStreamSource(repository.StreamSource):
1867
"""Stream data from a remote server."""
1869
def get_stream(self, search):
1870
if (self.from_repository._fallback_repositories and
1871
self.to_format._fetch_order == 'topological'):
1872
return self._real_stream(self.from_repository, search)
1875
repos = [self.from_repository]
1881
repos.extend(repo._fallback_repositories)
1882
sources.append(repo)
1883
return self.missing_parents_chain(search, sources)
1885
def get_stream_for_missing_keys(self, missing_keys):
1886
self.from_repository._ensure_real()
1887
real_repo = self.from_repository._real_repository
1888
real_source = real_repo._get_source(self.to_format)
1889
return real_source.get_stream_for_missing_keys(missing_keys)
1891
def _real_stream(self, repo, search):
1892
"""Get a stream for search from repo.
1894
This never called RemoteStreamSource.get_stream, and is a heler
1895
for RemoteStreamSource._get_stream to allow getting a stream
1896
reliably whether fallback back because of old servers or trying
1897
to stream from a non-RemoteRepository (which the stacked support
1900
source = repo._get_source(self.to_format)
1901
if isinstance(source, RemoteStreamSource):
1903
source = repo._real_repository._get_source(self.to_format)
1904
return source.get_stream(search)
1906
def _get_stream(self, repo, search):
1907
"""Core worker to get a stream from repo for search.
1909
This is used by both get_stream and the stacking support logic. It
1910
deliberately gets a stream for repo which does not need to be
1911
self.from_repository. In the event that repo is not Remote, or
1912
cannot do a smart stream, a fallback is made to the generic
1913
repository._get_stream() interface, via self._real_stream.
1915
In the event of stacking, streams from _get_stream will not
1916
contain all the data for search - this is normal (see get_stream).
1918
:param repo: A repository.
1919
:param search: A search.
1921
# Fallbacks may be non-smart
1922
if not isinstance(repo, RemoteRepository):
1923
return self._real_stream(repo, search)
1924
client = repo._client
1925
medium = client._medium
1926
path = repo.bzrdir._path_for_remote_call(client)
1927
search_bytes = repo._serialise_search_result(search)
1928
args = (path, self.to_format.network_name())
1930
('Repository.get_stream_1.19', (1, 19)),
1931
('Repository.get_stream', (1, 13))]
1933
for verb, version in candidate_verbs:
1934
if medium._is_remote_before(version):
1937
response = repo._call_with_body_bytes_expecting_body(
1938
verb, args, search_bytes)
1939
except errors.UnknownSmartMethod:
1940
medium._remember_remote_is_before(version)
1942
response_tuple, response_handler = response
1946
return self._real_stream(repo, search)
1947
if response_tuple[0] != 'ok':
1948
raise errors.UnexpectedSmartServerResponse(response_tuple)
1949
byte_stream = response_handler.read_streamed_body()
1950
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1951
if src_format.network_name() != repo._format.network_name():
1952
raise AssertionError(
1953
"Mismatched RemoteRepository and stream src %r, %r" % (
1954
src_format.network_name(), repo._format.network_name()))
1957
def missing_parents_chain(self, search, sources):
1958
"""Chain multiple streams together to handle stacking.
1960
:param search: The overall search to satisfy with streams.
1961
:param sources: A list of Repository objects to query.
1963
self.from_serialiser = self.from_repository._format._serializer
1964
self.seen_revs = set()
1965
self.referenced_revs = set()
1966
# If there are heads in the search, or the key count is > 0, we are not
1968
while not search.is_empty() and len(sources) > 1:
1969
source = sources.pop(0)
1970
stream = self._get_stream(source, search)
1971
for kind, substream in stream:
1972
if kind != 'revisions':
1973
yield kind, substream
1975
yield kind, self.missing_parents_rev_handler(substream)
1976
search = search.refine(self.seen_revs, self.referenced_revs)
1977
self.seen_revs = set()
1978
self.referenced_revs = set()
1979
if not search.is_empty():
1980
for kind, stream in self._get_stream(sources[0], search):
1983
def missing_parents_rev_handler(self, substream):
1984
for content in substream:
1985
revision_bytes = content.get_bytes_as('fulltext')
1986
revision = self.from_serialiser.read_revision_from_string(
1988
self.seen_revs.add(content.key[-1])
1989
self.referenced_revs.update(revision.parent_ids)
1993
class RemoteBranchLockableFiles(LockableFiles):
1994
"""A 'LockableFiles' implementation that talks to a smart server.
1996
This is not a public interface class.
1999
def __init__(self, bzrdir, _client):
2000
self.bzrdir = bzrdir
2001
self._client = _client
2002
self._need_find_modes = True
2003
LockableFiles.__init__(
2004
self, bzrdir.get_branch_transport(None),
2005
'lock', lockdir.LockDir)
2007
def _find_modes(self):
2008
# RemoteBranches don't let the client set the mode of control files.
2009
self._dir_mode = None
2010
self._file_mode = None
2013
class RemoteBranchFormat(branch.BranchFormat):
2015
def __init__(self, network_name=None):
2016
super(RemoteBranchFormat, self).__init__()
2017
self._matchingbzrdir = RemoteBzrDirFormat()
2018
self._matchingbzrdir.set_branch_format(self)
2019
self._custom_format = None
2020
self._network_name = network_name
2022
def __eq__(self, other):
2023
return (isinstance(other, RemoteBranchFormat) and
2024
self.__dict__ == other.__dict__)
2026
def _ensure_real(self):
2027
if self._custom_format is None:
2028
self._custom_format = branch.network_format_registry.get(
2031
def get_format_description(self):
2033
return 'Remote: ' + self._custom_format.get_format_description()
2035
def network_name(self):
2036
return self._network_name
2038
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2039
return a_bzrdir.open_branch(name=name,
2040
ignore_fallbacks=ignore_fallbacks)
2042
def _vfs_initialize(self, a_bzrdir, name):
2043
# Initialisation when using a local bzrdir object, or a non-vfs init
2044
# method is not available on the server.
2045
# self._custom_format is always set - the start of initialize ensures
2047
if isinstance(a_bzrdir, RemoteBzrDir):
2048
a_bzrdir._ensure_real()
2049
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2052
# We assume the bzrdir is parameterised; it may not be.
2053
result = self._custom_format.initialize(a_bzrdir, name)
2054
if (isinstance(a_bzrdir, RemoteBzrDir) and
2055
not isinstance(result, RemoteBranch)):
2056
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2060
def initialize(self, a_bzrdir, name=None):
2061
# 1) get the network name to use.
2062
if self._custom_format:
2063
network_name = self._custom_format.network_name()
2065
# Select the current bzrlib default and ask for that.
2066
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2067
reference_format = reference_bzrdir_format.get_branch_format()
2068
self._custom_format = reference_format
2069
network_name = reference_format.network_name()
2070
# Being asked to create on a non RemoteBzrDir:
2071
if not isinstance(a_bzrdir, RemoteBzrDir):
2072
return self._vfs_initialize(a_bzrdir, name=name)
2073
medium = a_bzrdir._client._medium
2074
if medium._is_remote_before((1, 13)):
2075
return self._vfs_initialize(a_bzrdir, name=name)
2076
# Creating on a remote bzr dir.
2077
# 2) try direct creation via RPC
2078
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2079
if name is not None:
2080
# XXX JRV20100304: Support creating colocated branches
2081
raise errors.NoColocatedBranchSupport(self)
2082
verb = 'BzrDir.create_branch'
2084
response = a_bzrdir._call(verb, path, network_name)
2085
except errors.UnknownSmartMethod:
2086
# Fallback - use vfs methods
2087
medium._remember_remote_is_before((1, 13))
2088
return self._vfs_initialize(a_bzrdir, name=name)
2089
if response[0] != 'ok':
2090
raise errors.UnexpectedSmartServerResponse(response)
2091
# Turn the response into a RemoteRepository object.
2092
format = RemoteBranchFormat(network_name=response[1])
2093
repo_format = response_tuple_to_repo_format(response[3:])
2094
if response[2] == '':
2095
repo_bzrdir = a_bzrdir
2097
repo_bzrdir = RemoteBzrDir(
2098
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2100
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2101
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2102
format=format, setup_stacking=False, name=name)
2103
# XXX: We know this is a new branch, so it must have revno 0, revid
2104
# NULL_REVISION. Creating the branch locked would make this be unable
2105
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2106
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2107
return remote_branch
2109
def make_tags(self, branch):
2111
return self._custom_format.make_tags(branch)
2113
def supports_tags(self):
2114
# Remote branches might support tags, but we won't know until we
2115
# access the real remote branch.
2117
return self._custom_format.supports_tags()
2119
def supports_stacking(self):
2121
return self._custom_format.supports_stacking()
2123
def supports_set_append_revisions_only(self):
2125
return self._custom_format.supports_set_append_revisions_only()
2128
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2129
"""Branch stored on a server accessed by HPSS RPC.
2131
At the moment most operations are mapped down to simple file operations.
2134
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2135
_client=None, format=None, setup_stacking=True, name=None):
2136
"""Create a RemoteBranch instance.
2138
:param real_branch: An optional local implementation of the branch
2139
format, usually accessing the data via the VFS.
2140
:param _client: Private parameter for testing.
2141
:param format: A RemoteBranchFormat object, None to create one
2142
automatically. If supplied it should have a network_name already
2144
:param setup_stacking: If True make an RPC call to determine the
2145
stacked (or not) status of the branch. If False assume the branch
2147
:param name: Colocated branch name
2149
# We intentionally don't call the parent class's __init__, because it
2150
# will try to assign to self.tags, which is a property in this subclass.
2151
# And the parent's __init__ doesn't do much anyway.
2152
self.bzrdir = remote_bzrdir
2153
if _client is not None:
2154
self._client = _client
2156
self._client = remote_bzrdir._client
2157
self.repository = remote_repository
2158
if real_branch is not None:
2159
self._real_branch = real_branch
2160
# Give the remote repository the matching real repo.
2161
real_repo = self._real_branch.repository
2162
if isinstance(real_repo, RemoteRepository):
2163
real_repo._ensure_real()
2164
real_repo = real_repo._real_repository
2165
self.repository._set_real_repository(real_repo)
2166
# Give the branch the remote repository to let fast-pathing happen.
2167
self._real_branch.repository = self.repository
2169
self._real_branch = None
2170
# Fill out expected attributes of branch for bzrlib API users.
2171
self._clear_cached_state()
2172
self.base = self.bzrdir.root_transport.base
2174
self._control_files = None
2175
self._lock_mode = None
2176
self._lock_token = None
2177
self._repo_lock_token = None
2178
self._lock_count = 0
2179
self._leave_lock = False
2180
# Setup a format: note that we cannot call _ensure_real until all the
2181
# attributes above are set: This code cannot be moved higher up in this
2184
self._format = RemoteBranchFormat()
2185
if real_branch is not None:
2186
self._format._network_name = \
2187
self._real_branch._format.network_name()
2189
self._format = format
2190
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2191
# branch.open_branch method.
2192
self._real_ignore_fallbacks = not setup_stacking
2193
if not self._format._network_name:
2194
# Did not get from open_branchV2 - old server.
2196
self._format._network_name = \
2197
self._real_branch._format.network_name()
2198
self.tags = self._format.make_tags(self)
2199
# The base class init is not called, so we duplicate this:
2200
hooks = branch.Branch.hooks['open']
2203
self._is_stacked = False
2205
self._setup_stacking()
2207
def _setup_stacking(self):
2208
# configure stacking into the remote repository, by reading it from
2211
fallback_url = self.get_stacked_on_url()
2212
except (errors.NotStacked, errors.UnstackableBranchFormat,
2213
errors.UnstackableRepositoryFormat), e:
2215
self._is_stacked = True
2216
self._activate_fallback_location(fallback_url)
2218
def _get_config(self):
2219
return RemoteBranchConfig(self)
2221
def _get_real_transport(self):
2222
# if we try vfs access, return the real branch's vfs transport
2224
return self._real_branch._transport
2226
_transport = property(_get_real_transport)
2229
return "%s(%s)" % (self.__class__.__name__, self.base)
2233
def _ensure_real(self):
2234
"""Ensure that there is a _real_branch set.
2236
Used before calls to self._real_branch.
2238
if self._real_branch is None:
2239
if not vfs.vfs_enabled():
2240
raise AssertionError('smart server vfs must be enabled '
2241
'to use vfs implementation')
2242
self.bzrdir._ensure_real()
2243
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2244
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2245
if self.repository._real_repository is None:
2246
# Give the remote repository the matching real repo.
2247
real_repo = self._real_branch.repository
2248
if isinstance(real_repo, RemoteRepository):
2249
real_repo._ensure_real()
2250
real_repo = real_repo._real_repository
2251
self.repository._set_real_repository(real_repo)
2252
# Give the real branch the remote repository to let fast-pathing
2254
self._real_branch.repository = self.repository
2255
if self._lock_mode == 'r':
2256
self._real_branch.lock_read()
2257
elif self._lock_mode == 'w':
2258
self._real_branch.lock_write(token=self._lock_token)
2260
def _translate_error(self, err, **context):
2261
self.repository._translate_error(err, branch=self, **context)
2263
def _clear_cached_state(self):
2264
super(RemoteBranch, self)._clear_cached_state()
2265
if self._real_branch is not None:
2266
self._real_branch._clear_cached_state()
2268
def _clear_cached_state_of_remote_branch_only(self):
2269
"""Like _clear_cached_state, but doesn't clear the cache of
2272
This is useful when falling back to calling a method of
2273
self._real_branch that changes state. In that case the underlying
2274
branch changes, so we need to invalidate this RemoteBranch's cache of
2275
it. However, there's no need to invalidate the _real_branch's cache
2276
too, in fact doing so might harm performance.
2278
super(RemoteBranch, self)._clear_cached_state()
2281
def control_files(self):
2282
# Defer actually creating RemoteBranchLockableFiles until its needed,
2283
# because it triggers an _ensure_real that we otherwise might not need.
2284
if self._control_files is None:
2285
self._control_files = RemoteBranchLockableFiles(
2286
self.bzrdir, self._client)
2287
return self._control_files
2289
def _get_checkout_format(self):
2291
return self._real_branch._get_checkout_format()
2293
def get_physical_lock_status(self):
2294
"""See Branch.get_physical_lock_status()."""
2295
# should be an API call to the server, as branches must be lockable.
2297
return self._real_branch.get_physical_lock_status()
2299
def get_stacked_on_url(self):
2300
"""Get the URL this branch is stacked against.
2302
:raises NotStacked: If the branch is not stacked.
2303
:raises UnstackableBranchFormat: If the branch does not support
2305
:raises UnstackableRepositoryFormat: If the repository does not support
2309
# there may not be a repository yet, so we can't use
2310
# self._translate_error, so we can't use self._call either.
2311
response = self._client.call('Branch.get_stacked_on_url',
2312
self._remote_path())
2313
except errors.ErrorFromSmartServer, err:
2314
# there may not be a repository yet, so we can't call through
2315
# its _translate_error
2316
_translate_error(err, branch=self)
2317
except errors.UnknownSmartMethod, err:
2319
return self._real_branch.get_stacked_on_url()
2320
if response[0] != 'ok':
2321
raise errors.UnexpectedSmartServerResponse(response)
2324
def set_stacked_on_url(self, url):
2325
branch.Branch.set_stacked_on_url(self, url)
2327
self._is_stacked = False
2329
self._is_stacked = True
2331
def _vfs_get_tags_bytes(self):
2333
return self._real_branch._get_tags_bytes()
2335
def _get_tags_bytes(self):
2336
medium = self._client._medium
2337
if medium._is_remote_before((1, 13)):
2338
return self._vfs_get_tags_bytes()
2340
response = self._call('Branch.get_tags_bytes', self._remote_path())
2341
except errors.UnknownSmartMethod:
2342
medium._remember_remote_is_before((1, 13))
2343
return self._vfs_get_tags_bytes()
2346
def _vfs_set_tags_bytes(self, bytes):
2348
return self._real_branch._set_tags_bytes(bytes)
2350
def _set_tags_bytes(self, bytes):
2351
medium = self._client._medium
2352
if medium._is_remote_before((1, 18)):
2353
self._vfs_set_tags_bytes(bytes)
2357
self._remote_path(), self._lock_token, self._repo_lock_token)
2358
response = self._call_with_body_bytes(
2359
'Branch.set_tags_bytes', args, bytes)
2360
except errors.UnknownSmartMethod:
2361
medium._remember_remote_is_before((1, 18))
2362
self._vfs_set_tags_bytes(bytes)
2364
def lock_read(self):
2365
self.repository.lock_read()
2366
if not self._lock_mode:
2367
self._note_lock('r')
2368
self._lock_mode = 'r'
2369
self._lock_count = 1
2370
if self._real_branch is not None:
2371
self._real_branch.lock_read()
2373
self._lock_count += 1
2375
def _remote_lock_write(self, token):
2377
branch_token = repo_token = ''
2379
branch_token = token
2380
repo_token = self.repository.lock_write()
2381
self.repository.unlock()
2382
err_context = {'token': token}
2383
response = self._call(
2384
'Branch.lock_write', self._remote_path(), branch_token,
2385
repo_token or '', **err_context)
2386
if response[0] != 'ok':
2387
raise errors.UnexpectedSmartServerResponse(response)
2388
ok, branch_token, repo_token = response
2389
return branch_token, repo_token
2391
def lock_write(self, token=None):
2392
if not self._lock_mode:
2393
self._note_lock('w')
2394
# Lock the branch and repo in one remote call.
2395
remote_tokens = self._remote_lock_write(token)
2396
self._lock_token, self._repo_lock_token = remote_tokens
2397
if not self._lock_token:
2398
raise SmartProtocolError('Remote server did not return a token!')
2399
# Tell the self.repository object that it is locked.
2400
self.repository.lock_write(
2401
self._repo_lock_token, _skip_rpc=True)
2403
if self._real_branch is not None:
2404
self._real_branch.lock_write(token=self._lock_token)
2405
if token is not None:
2406
self._leave_lock = True
2408
self._leave_lock = False
2409
self._lock_mode = 'w'
2410
self._lock_count = 1
2411
elif self._lock_mode == 'r':
2412
raise errors.ReadOnlyTransaction
2414
if token is not None:
2415
# A token was given to lock_write, and we're relocking, so
2416
# check that the given token actually matches the one we
2418
if token != self._lock_token:
2419
raise errors.TokenMismatch(token, self._lock_token)
2420
self._lock_count += 1
2421
# Re-lock the repository too.
2422
self.repository.lock_write(self._repo_lock_token)
2423
return self._lock_token or None
2425
def _unlock(self, branch_token, repo_token):
2426
err_context = {'token': str((branch_token, repo_token))}
2427
response = self._call(
2428
'Branch.unlock', self._remote_path(), branch_token,
2429
repo_token or '', **err_context)
2430
if response == ('ok',):
2432
raise errors.UnexpectedSmartServerResponse(response)
2434
@only_raises(errors.LockNotHeld, errors.LockBroken)
2437
self._lock_count -= 1
2438
if not self._lock_count:
2439
self._clear_cached_state()
2440
mode = self._lock_mode
2441
self._lock_mode = None
2442
if self._real_branch is not None:
2443
if (not self._leave_lock and mode == 'w' and
2444
self._repo_lock_token):
2445
# If this RemoteBranch will remove the physical lock
2446
# for the repository, make sure the _real_branch
2447
# doesn't do it first. (Because the _real_branch's
2448
# repository is set to be the RemoteRepository.)
2449
self._real_branch.repository.leave_lock_in_place()
2450
self._real_branch.unlock()
2452
# Only write-locked branched need to make a remote method
2453
# call to perform the unlock.
2455
if not self._lock_token:
2456
raise AssertionError('Locked, but no token!')
2457
branch_token = self._lock_token
2458
repo_token = self._repo_lock_token
2459
self._lock_token = None
2460
self._repo_lock_token = None
2461
if not self._leave_lock:
2462
self._unlock(branch_token, repo_token)
2464
self.repository.unlock()
2466
def break_lock(self):
2468
return self._real_branch.break_lock()
2470
def leave_lock_in_place(self):
2471
if not self._lock_token:
2472
raise NotImplementedError(self.leave_lock_in_place)
2473
self._leave_lock = True
2475
def dont_leave_lock_in_place(self):
2476
if not self._lock_token:
2477
raise NotImplementedError(self.dont_leave_lock_in_place)
2478
self._leave_lock = False
2481
def get_rev_id(self, revno, history=None):
2483
return _mod_revision.NULL_REVISION
2484
last_revision_info = self.last_revision_info()
2485
ok, result = self.repository.get_rev_id_for_revno(
2486
revno, last_revision_info)
2489
missing_parent = result[1]
2490
# Either the revision named by the server is missing, or its parent
2491
# is. Call get_parent_map to determine which, so that we report a
2493
parent_map = self.repository.get_parent_map([missing_parent])
2494
if missing_parent in parent_map:
2495
missing_parent = parent_map[missing_parent]
2496
raise errors.RevisionNotPresent(missing_parent, self.repository)
2498
def _last_revision_info(self):
2499
response = self._call('Branch.last_revision_info', self._remote_path())
2500
if response[0] != 'ok':
2501
raise SmartProtocolError('unexpected response code %s' % (response,))
2502
revno = int(response[1])
2503
last_revision = response[2]
2504
return (revno, last_revision)
2506
def _gen_revision_history(self):
2507
"""See Branch._gen_revision_history()."""
2508
if self._is_stacked:
2510
return self._real_branch._gen_revision_history()
2511
response_tuple, response_handler = self._call_expecting_body(
2512
'Branch.revision_history', self._remote_path())
2513
if response_tuple[0] != 'ok':
2514
raise errors.UnexpectedSmartServerResponse(response_tuple)
2515
result = response_handler.read_body_bytes().split('\x00')
2520
def _remote_path(self):
2521
return self.bzrdir._path_for_remote_call(self._client)
2523
def _set_last_revision_descendant(self, revision_id, other_branch,
2524
allow_diverged=False, allow_overwrite_descendant=False):
2525
# This performs additional work to meet the hook contract; while its
2526
# undesirable, we have to synthesise the revno to call the hook, and
2527
# not calling the hook is worse as it means changes can't be prevented.
2528
# Having calculated this though, we can't just call into
2529
# set_last_revision_info as a simple call, because there is a set_rh
2530
# hook that some folk may still be using.
2531
old_revno, old_revid = self.last_revision_info()
2532
history = self._lefthand_history(revision_id)
2533
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2534
err_context = {'other_branch': other_branch}
2535
response = self._call('Branch.set_last_revision_ex',
2536
self._remote_path(), self._lock_token, self._repo_lock_token,
2537
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2539
self._clear_cached_state()
2540
if len(response) != 3 and response[0] != 'ok':
2541
raise errors.UnexpectedSmartServerResponse(response)
2542
new_revno, new_revision_id = response[1:]
2543
self._last_revision_info_cache = new_revno, new_revision_id
2544
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2545
if self._real_branch is not None:
2546
cache = new_revno, new_revision_id
2547
self._real_branch._last_revision_info_cache = cache
2549
def _set_last_revision(self, revision_id):
2550
old_revno, old_revid = self.last_revision_info()
2551
# This performs additional work to meet the hook contract; while its
2552
# undesirable, we have to synthesise the revno to call the hook, and
2553
# not calling the hook is worse as it means changes can't be prevented.
2554
# Having calculated this though, we can't just call into
2555
# set_last_revision_info as a simple call, because there is a set_rh
2556
# hook that some folk may still be using.
2557
history = self._lefthand_history(revision_id)
2558
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2559
self._clear_cached_state()
2560
response = self._call('Branch.set_last_revision',
2561
self._remote_path(), self._lock_token, self._repo_lock_token,
2563
if response != ('ok',):
2564
raise errors.UnexpectedSmartServerResponse(response)
2565
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2568
def set_revision_history(self, rev_history):
2569
# Send just the tip revision of the history; the server will generate
2570
# the full history from that. If the revision doesn't exist in this
2571
# branch, NoSuchRevision will be raised.
2572
if rev_history == []:
2575
rev_id = rev_history[-1]
2576
self._set_last_revision(rev_id)
2577
for hook in branch.Branch.hooks['set_rh']:
2578
hook(self, rev_history)
2579
self._cache_revision_history(rev_history)
2581
def _get_parent_location(self):
2582
medium = self._client._medium
2583
if medium._is_remote_before((1, 13)):
2584
return self._vfs_get_parent_location()
2586
response = self._call('Branch.get_parent', self._remote_path())
2587
except errors.UnknownSmartMethod:
2588
medium._remember_remote_is_before((1, 13))
2589
return self._vfs_get_parent_location()
2590
if len(response) != 1:
2591
raise errors.UnexpectedSmartServerResponse(response)
2592
parent_location = response[0]
2593
if parent_location == '':
2595
return parent_location
2597
def _vfs_get_parent_location(self):
2599
return self._real_branch._get_parent_location()
2601
def _set_parent_location(self, url):
2602
medium = self._client._medium
2603
if medium._is_remote_before((1, 15)):
2604
return self._vfs_set_parent_location(url)
2606
call_url = url or ''
2607
if type(call_url) is not str:
2608
raise AssertionError('url must be a str or None (%s)' % url)
2609
response = self._call('Branch.set_parent_location',
2610
self._remote_path(), self._lock_token, self._repo_lock_token,
2612
except errors.UnknownSmartMethod:
2613
medium._remember_remote_is_before((1, 15))
2614
return self._vfs_set_parent_location(url)
2616
raise errors.UnexpectedSmartServerResponse(response)
2618
def _vfs_set_parent_location(self, url):
2620
return self._real_branch._set_parent_location(url)
2623
def pull(self, source, overwrite=False, stop_revision=None,
2625
self._clear_cached_state_of_remote_branch_only()
2627
return self._real_branch.pull(
2628
source, overwrite=overwrite, stop_revision=stop_revision,
2629
_override_hook_target=self, **kwargs)
2632
def push(self, target, overwrite=False, stop_revision=None):
2634
return self._real_branch.push(
2635
target, overwrite=overwrite, stop_revision=stop_revision,
2636
_override_hook_source_branch=self)
2638
def is_locked(self):
2639
return self._lock_count >= 1
2642
def revision_id_to_revno(self, revision_id):
2644
return self._real_branch.revision_id_to_revno(revision_id)
2647
def set_last_revision_info(self, revno, revision_id):
2648
# XXX: These should be returned by the set_last_revision_info verb
2649
old_revno, old_revid = self.last_revision_info()
2650
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2651
revision_id = ensure_null(revision_id)
2653
response = self._call('Branch.set_last_revision_info',
2654
self._remote_path(), self._lock_token, self._repo_lock_token,
2655
str(revno), revision_id)
2656
except errors.UnknownSmartMethod:
2658
self._clear_cached_state_of_remote_branch_only()
2659
self._real_branch.set_last_revision_info(revno, revision_id)
2660
self._last_revision_info_cache = revno, revision_id
2662
if response == ('ok',):
2663
self._clear_cached_state()
2664
self._last_revision_info_cache = revno, revision_id
2665
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2666
# Update the _real_branch's cache too.
2667
if self._real_branch is not None:
2668
cache = self._last_revision_info_cache
2669
self._real_branch._last_revision_info_cache = cache
2671
raise errors.UnexpectedSmartServerResponse(response)
2674
def generate_revision_history(self, revision_id, last_rev=None,
2676
medium = self._client._medium
2677
if not medium._is_remote_before((1, 6)):
2678
# Use a smart method for 1.6 and above servers
2680
self._set_last_revision_descendant(revision_id, other_branch,
2681
allow_diverged=True, allow_overwrite_descendant=True)
2683
except errors.UnknownSmartMethod:
2684
medium._remember_remote_is_before((1, 6))
2685
self._clear_cached_state_of_remote_branch_only()
2686
self.set_revision_history(self._lefthand_history(revision_id,
2687
last_rev=last_rev,other_branch=other_branch))
2689
def set_push_location(self, location):
2691
return self._real_branch.set_push_location(location)
2694
class RemoteConfig(object):
2695
"""A Config that reads and writes from smart verbs.
2697
It is a low-level object that considers config data to be name/value pairs
2698
that may be associated with a section. Assigning meaning to the these
2699
values is done at higher levels like bzrlib.config.TreeConfig.
2702
def get_option(self, name, section=None, default=None):
2703
"""Return the value associated with a named option.
2705
:param name: The name of the value
2706
:param section: The section the option is in (if any)
2707
:param default: The value to return if the value is not set
2708
:return: The value or default value
2711
configobj = self._get_configobj()
2713
section_obj = configobj
2716
section_obj = configobj[section]
2719
return section_obj.get(name, default)
2720
except errors.UnknownSmartMethod:
2721
return self._vfs_get_option(name, section, default)
2723
def _response_to_configobj(self, response):
2724
if len(response[0]) and response[0][0] != 'ok':
2725
raise errors.UnexpectedSmartServerResponse(response)
2726
lines = response[1].read_body_bytes().splitlines()
2727
return config.ConfigObj(lines, encoding='utf-8')
2730
class RemoteBranchConfig(RemoteConfig):
2731
"""A RemoteConfig for Branches."""
2733
def __init__(self, branch):
2734
self._branch = branch
2736
def _get_configobj(self):
2737
path = self._branch._remote_path()
2738
response = self._branch._client.call_expecting_body(
2739
'Branch.get_config_file', path)
2740
return self._response_to_configobj(response)
2742
def set_option(self, value, name, section=None):
2743
"""Set the value associated with a named option.
2745
:param value: The value to set
2746
:param name: The name of the value to set
2747
:param section: The section the option is in (if any)
2749
medium = self._branch._client._medium
2750
if medium._is_remote_before((1, 14)):
2751
return self._vfs_set_option(value, name, section)
2753
path = self._branch._remote_path()
2754
response = self._branch._client.call('Branch.set_config_option',
2755
path, self._branch._lock_token, self._branch._repo_lock_token,
2756
value.encode('utf8'), name, section or '')
2757
except errors.UnknownSmartMethod:
2758
medium._remember_remote_is_before((1, 14))
2759
return self._vfs_set_option(value, name, section)
2761
raise errors.UnexpectedSmartServerResponse(response)
2763
def _real_object(self):
2764
self._branch._ensure_real()
2765
return self._branch._real_branch
2767
def _vfs_set_option(self, value, name, section=None):
2768
return self._real_object()._get_config().set_option(
2769
value, name, section)
2772
class RemoteBzrDirConfig(RemoteConfig):
2773
"""A RemoteConfig for BzrDirs."""
2775
def __init__(self, bzrdir):
2776
self._bzrdir = bzrdir
2778
def _get_configobj(self):
2779
medium = self._bzrdir._client._medium
2780
verb = 'BzrDir.get_config_file'
2781
if medium._is_remote_before((1, 15)):
2782
raise errors.UnknownSmartMethod(verb)
2783
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2784
response = self._bzrdir._call_expecting_body(
2786
return self._response_to_configobj(response)
2788
def _vfs_get_option(self, name, section, default):
2789
return self._real_object()._get_config().get_option(
2790
name, section, default)
2792
def set_option(self, value, name, section=None):
2793
"""Set the value associated with a named option.
2795
:param value: The value to set
2796
:param name: The name of the value to set
2797
:param section: The section the option is in (if any)
2799
return self._real_object()._get_config().set_option(
2800
value, name, section)
2802
def _real_object(self):
2803
self._bzrdir._ensure_real()
2804
return self._bzrdir._real_bzrdir
2808
def _extract_tar(tar, to_dir):
2809
"""Extract all the contents of a tarfile object.
2811
A replacement for extractall, which is not present in python2.4
2814
tar.extract(tarinfo, to_dir)
2817
def _translate_error(err, **context):
2818
"""Translate an ErrorFromSmartServer into a more useful error.
2820
Possible context keys:
2828
If the error from the server doesn't match a known pattern, then
2829
UnknownErrorFromSmartServer is raised.
2833
return context[name]
2834
except KeyError, key_err:
2835
mutter('Missing key %r in context %r', key_err.args[0], context)
2838
"""Get the path from the context if present, otherwise use first error
2842
return context['path']
2843
except KeyError, key_err:
2845
return err.error_args[0]
2846
except IndexError, idx_err:
2848
'Missing key %r in context %r', key_err.args[0], context)
2851
if err.error_verb == 'IncompatibleRepositories':
2852
raise errors.IncompatibleRepositories(err.error_args[0],
2853
err.error_args[1], err.error_args[2])
2854
elif err.error_verb == 'NoSuchRevision':
2855
raise NoSuchRevision(find('branch'), err.error_args[0])
2856
elif err.error_verb == 'nosuchrevision':
2857
raise NoSuchRevision(find('repository'), err.error_args[0])
2858
elif err.error_verb == 'nobranch':
2859
if len(err.error_args) >= 1:
2860
extra = err.error_args[0]
2863
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2865
elif err.error_verb == 'norepository':
2866
raise errors.NoRepositoryPresent(find('bzrdir'))
2867
elif err.error_verb == 'LockContention':
2868
raise errors.LockContention('(remote lock)')
2869
elif err.error_verb == 'UnlockableTransport':
2870
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2871
elif err.error_verb == 'LockFailed':
2872
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2873
elif err.error_verb == 'TokenMismatch':
2874
raise errors.TokenMismatch(find('token'), '(remote token)')
2875
elif err.error_verb == 'Diverged':
2876
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2877
elif err.error_verb == 'TipChangeRejected':
2878
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2879
elif err.error_verb == 'UnstackableBranchFormat':
2880
raise errors.UnstackableBranchFormat(*err.error_args)
2881
elif err.error_verb == 'UnstackableRepositoryFormat':
2882
raise errors.UnstackableRepositoryFormat(*err.error_args)
2883
elif err.error_verb == 'NotStacked':
2884
raise errors.NotStacked(branch=find('branch'))
2885
elif err.error_verb == 'PermissionDenied':
2887
if len(err.error_args) >= 2:
2888
extra = err.error_args[1]
2891
raise errors.PermissionDenied(path, extra=extra)
2892
elif err.error_verb == 'ReadError':
2894
raise errors.ReadError(path)
2895
elif err.error_verb == 'NoSuchFile':
2897
raise errors.NoSuchFile(path)
2898
elif err.error_verb == 'FileExists':
2899
raise errors.FileExists(err.error_args[0])
2900
elif err.error_verb == 'DirectoryNotEmpty':
2901
raise errors.DirectoryNotEmpty(err.error_args[0])
2902
elif err.error_verb == 'ShortReadvError':
2903
args = err.error_args
2904
raise errors.ShortReadvError(
2905
args[0], int(args[1]), int(args[2]), int(args[3]))
2906
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2907
encoding = str(err.error_args[0]) # encoding must always be a string
2908
val = err.error_args[1]
2909
start = int(err.error_args[2])
2910
end = int(err.error_args[3])
2911
reason = str(err.error_args[4]) # reason must always be a string
2912
if val.startswith('u:'):
2913
val = val[2:].decode('utf-8')
2914
elif val.startswith('s:'):
2915
val = val[2:].decode('base64')
2916
if err.error_verb == 'UnicodeDecodeError':
2917
raise UnicodeDecodeError(encoding, val, start, end, reason)
2918
elif err.error_verb == 'UnicodeEncodeError':
2919
raise UnicodeEncodeError(encoding, val, start, end, reason)
2920
elif err.error_verb == 'ReadOnlyError':
2921
raise errors.TransportNotPossible('readonly transport')
2922
raise errors.UnknownErrorFromSmartServer(err)