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
bzrdir.ControlComponent):
647
"""Repository accessed over rpc.
649
For the moment most operations are performed using local transport-backed
653
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
654
"""Create a RemoteRepository instance.
656
:param remote_bzrdir: The bzrdir hosting this repository.
657
:param format: The RemoteFormat object to use.
658
:param real_repository: If not None, a local implementation of the
659
repository logic for the repository, usually accessing the data
661
:param _client: Private testing parameter - override the smart client
662
to be used by the repository.
665
self._real_repository = real_repository
667
self._real_repository = None
668
self.bzrdir = remote_bzrdir
670
self._client = remote_bzrdir._client
672
self._client = _client
673
self._format = format
674
self._lock_mode = None
675
self._lock_token = None
677
self._leave_lock = False
678
# Cache of revision parents; misses are cached during read locks, and
679
# write locks when no _real_repository has been set.
680
self._unstacked_provider = graph.CachingParentsProvider(
681
get_parent_map=self._get_parent_map_rpc)
682
self._unstacked_provider.disable_cache()
684
# These depend on the actual remote format, so force them off for
685
# maximum compatibility. XXX: In future these should depend on the
686
# remote repository instance, but this is irrelevant until we perform
687
# reconcile via an RPC call.
688
self._reconcile_does_inventory_gc = False
689
self._reconcile_fixes_text_parents = False
690
self._reconcile_backsup_inventory = False
691
self.base = self.bzrdir.transport.base
692
# Additional places to query for data.
693
self._fallback_repositories = []
696
def user_transport(self):
697
return self.bzrdir.user_transport
700
def control_transport(self):
701
# XXX: Normally you shouldn't directly get at the remote repository
702
# transport, but I'm not sure it's worth making this method
703
# optional -- mbp 2010-04-21
704
return self.bzrdir.get_repository_transport(None)
707
return "%s(%s)" % (self.__class__.__name__, self.base)
711
def abort_write_group(self, suppress_errors=False):
712
"""Complete a write group on the decorated repository.
714
Smart methods perform operations in a single step so this API
715
is not really applicable except as a compatibility thunk
716
for older plugins that don't use e.g. the CommitBuilder
719
:param suppress_errors: see Repository.abort_write_group.
722
return self._real_repository.abort_write_group(
723
suppress_errors=suppress_errors)
727
"""Decorate the real repository for now.
729
In the long term a full blown network facility is needed to avoid
730
creating a real repository object locally.
733
return self._real_repository.chk_bytes
735
def commit_write_group(self):
736
"""Complete a write group on the decorated repository.
738
Smart methods perform operations in a single step so this API
739
is not really applicable except as a compatibility thunk
740
for older plugins that don't use e.g. the CommitBuilder
744
return self._real_repository.commit_write_group()
746
def resume_write_group(self, tokens):
748
return self._real_repository.resume_write_group(tokens)
750
def suspend_write_group(self):
752
return self._real_repository.suspend_write_group()
754
def get_missing_parent_inventories(self, check_for_missing_texts=True):
756
return self._real_repository.get_missing_parent_inventories(
757
check_for_missing_texts=check_for_missing_texts)
759
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
761
return self._real_repository.get_rev_id_for_revno(
764
def get_rev_id_for_revno(self, revno, known_pair):
765
"""See Repository.get_rev_id_for_revno."""
766
path = self.bzrdir._path_for_remote_call(self._client)
768
if self._client._medium._is_remote_before((1, 17)):
769
return self._get_rev_id_for_revno_vfs(revno, known_pair)
770
response = self._call(
771
'Repository.get_rev_id_for_revno', path, revno, known_pair)
772
except errors.UnknownSmartMethod:
773
self._client._medium._remember_remote_is_before((1, 17))
774
return self._get_rev_id_for_revno_vfs(revno, known_pair)
775
if response[0] == 'ok':
776
return True, response[1]
777
elif response[0] == 'history-incomplete':
778
known_pair = response[1:3]
779
for fallback in self._fallback_repositories:
780
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
785
# Not found in any fallbacks
786
return False, known_pair
788
raise errors.UnexpectedSmartServerResponse(response)
790
def _ensure_real(self):
791
"""Ensure that there is a _real_repository set.
793
Used before calls to self._real_repository.
795
Note that _ensure_real causes many roundtrips to the server which are
796
not desirable, and prevents the use of smart one-roundtrip RPC's to
797
perform complex operations (such as accessing parent data, streaming
798
revisions etc). Adding calls to _ensure_real should only be done when
799
bringing up new functionality, adding fallbacks for smart methods that
800
require a fallback path, and never to replace an existing smart method
801
invocation. If in doubt chat to the bzr network team.
803
if self._real_repository is None:
804
if 'hpssvfs' in debug.debug_flags:
806
warning('VFS Repository access triggered\n%s',
807
''.join(traceback.format_stack()))
808
self._unstacked_provider.missing_keys.clear()
809
self.bzrdir._ensure_real()
810
self._set_real_repository(
811
self.bzrdir._real_bzrdir.open_repository())
813
def _translate_error(self, err, **context):
814
self.bzrdir._translate_error(err, repository=self, **context)
816
def find_text_key_references(self):
817
"""Find the text key references within the repository.
819
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
820
revision_ids. Each altered file-ids has the exact revision_ids that
821
altered it listed explicitly.
822
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
823
to whether they were referred to by the inventory of the
824
revision_id that they contain. The inventory texts from all present
825
revision ids are assessed to generate this report.
828
return self._real_repository.find_text_key_references()
830
def _generate_text_key_index(self):
831
"""Generate a new text key index for the repository.
833
This is an expensive function that will take considerable time to run.
835
:return: A dict mapping (file_id, revision_id) tuples to a list of
836
parents, also (file_id, revision_id) tuples.
839
return self._real_repository._generate_text_key_index()
841
def _get_revision_graph(self, revision_id):
842
"""Private method for using with old (< 1.2) servers to fallback."""
843
if revision_id is None:
845
elif revision.is_null(revision_id):
848
path = self.bzrdir._path_for_remote_call(self._client)
849
response = self._call_expecting_body(
850
'Repository.get_revision_graph', path, revision_id)
851
response_tuple, response_handler = response
852
if response_tuple[0] != 'ok':
853
raise errors.UnexpectedSmartServerResponse(response_tuple)
854
coded = response_handler.read_body_bytes()
856
# no revisions in this repository!
858
lines = coded.split('\n')
861
d = tuple(line.split())
862
revision_graph[d[0]] = d[1:]
864
return revision_graph
867
"""See Repository._get_sink()."""
868
return RemoteStreamSink(self)
870
def _get_source(self, to_format):
871
"""Return a source for streaming from this repository."""
872
return RemoteStreamSource(self, to_format)
875
def has_revision(self, revision_id):
876
"""True if this repository has a copy of the revision."""
877
# Copy of bzrlib.repository.Repository.has_revision
878
return revision_id in self.has_revisions((revision_id,))
881
def has_revisions(self, revision_ids):
882
"""Probe to find out the presence of multiple revisions.
884
:param revision_ids: An iterable of revision_ids.
885
:return: A set of the revision_ids that were present.
887
# Copy of bzrlib.repository.Repository.has_revisions
888
parent_map = self.get_parent_map(revision_ids)
889
result = set(parent_map)
890
if _mod_revision.NULL_REVISION in revision_ids:
891
result.add(_mod_revision.NULL_REVISION)
894
def _has_same_fallbacks(self, other_repo):
895
"""Returns true if the repositories have the same fallbacks."""
896
# XXX: copied from Repository; it should be unified into a base class
897
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
898
my_fb = self._fallback_repositories
899
other_fb = other_repo._fallback_repositories
900
if len(my_fb) != len(other_fb):
902
for f, g in zip(my_fb, other_fb):
903
if not f.has_same_location(g):
907
def has_same_location(self, other):
908
# TODO: Move to RepositoryBase and unify with the regular Repository
909
# one; unfortunately the tests rely on slightly different behaviour at
910
# present -- mbp 20090710
911
return (self.__class__ is other.__class__ and
912
self.bzrdir.transport.base == other.bzrdir.transport.base)
914
def get_graph(self, other_repository=None):
915
"""Return the graph for this repository format"""
916
parents_provider = self._make_parents_provider(other_repository)
917
return graph.Graph(parents_provider)
920
def get_known_graph_ancestry(self, revision_ids):
921
"""Return the known graph for a set of revision ids and their ancestors.
923
st = static_tuple.StaticTuple
924
revision_keys = [st(r_id).intern() for r_id in revision_ids]
925
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
926
return graph.GraphThunkIdsToKeys(known_graph)
928
def gather_stats(self, revid=None, committers=None):
929
"""See Repository.gather_stats()."""
930
path = self.bzrdir._path_for_remote_call(self._client)
931
# revid can be None to indicate no revisions, not just NULL_REVISION
932
if revid is None or revision.is_null(revid):
936
if committers is None or not committers:
937
fmt_committers = 'no'
939
fmt_committers = 'yes'
940
response_tuple, response_handler = self._call_expecting_body(
941
'Repository.gather_stats', path, fmt_revid, fmt_committers)
942
if response_tuple[0] != 'ok':
943
raise errors.UnexpectedSmartServerResponse(response_tuple)
945
body = response_handler.read_body_bytes()
947
for line in body.split('\n'):
950
key, val_text = line.split(':')
951
if key in ('revisions', 'size', 'committers'):
952
result[key] = int(val_text)
953
elif key in ('firstrev', 'latestrev'):
954
values = val_text.split(' ')[1:]
955
result[key] = (float(values[0]), long(values[1]))
959
def find_branches(self, using=False):
960
"""See Repository.find_branches()."""
961
# should be an API call to the server.
963
return self._real_repository.find_branches(using=using)
965
def get_physical_lock_status(self):
966
"""See Repository.get_physical_lock_status()."""
967
# should be an API call to the server.
969
return self._real_repository.get_physical_lock_status()
971
def is_in_write_group(self):
972
"""Return True if there is an open write group.
974
write groups are only applicable locally for the smart server..
976
if self._real_repository:
977
return self._real_repository.is_in_write_group()
980
return self._lock_count >= 1
983
"""See Repository.is_shared()."""
984
path = self.bzrdir._path_for_remote_call(self._client)
985
response = self._call('Repository.is_shared', path)
986
if response[0] not in ('yes', 'no'):
987
raise SmartProtocolError('unexpected response code %s' % (response,))
988
return response[0] == 'yes'
990
def is_write_locked(self):
991
return self._lock_mode == 'w'
993
def _warn_if_deprecated(self, branch=None):
994
# If we have a real repository, the check will be done there, if we
995
# don't the check will be done remotely.
999
# wrong eventually - want a local lock cache context
1000
if not self._lock_mode:
1001
self._note_lock('r')
1002
self._lock_mode = 'r'
1003
self._lock_count = 1
1004
self._unstacked_provider.enable_cache(cache_misses=True)
1005
if self._real_repository is not None:
1006
self._real_repository.lock_read()
1007
for repo in self._fallback_repositories:
1010
self._lock_count += 1
1012
def _remote_lock_write(self, token):
1013
path = self.bzrdir._path_for_remote_call(self._client)
1016
err_context = {'token': token}
1017
response = self._call('Repository.lock_write', path, token,
1019
if response[0] == 'ok':
1020
ok, token = response
1023
raise errors.UnexpectedSmartServerResponse(response)
1025
def lock_write(self, token=None, _skip_rpc=False):
1026
if not self._lock_mode:
1027
self._note_lock('w')
1029
if self._lock_token is not None:
1030
if token != self._lock_token:
1031
raise errors.TokenMismatch(token, self._lock_token)
1032
self._lock_token = token
1034
self._lock_token = self._remote_lock_write(token)
1035
# if self._lock_token is None, then this is something like packs or
1036
# svn where we don't get to lock the repo, or a weave style repository
1037
# where we cannot lock it over the wire and attempts to do so will
1039
if self._real_repository is not None:
1040
self._real_repository.lock_write(token=self._lock_token)
1041
if token is not None:
1042
self._leave_lock = True
1044
self._leave_lock = False
1045
self._lock_mode = 'w'
1046
self._lock_count = 1
1047
cache_misses = self._real_repository is None
1048
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1049
for repo in self._fallback_repositories:
1050
# Writes don't affect fallback repos
1052
elif self._lock_mode == 'r':
1053
raise errors.ReadOnlyError(self)
1055
self._lock_count += 1
1056
return self._lock_token or None
1058
def leave_lock_in_place(self):
1059
if not self._lock_token:
1060
raise NotImplementedError(self.leave_lock_in_place)
1061
self._leave_lock = True
1063
def dont_leave_lock_in_place(self):
1064
if not self._lock_token:
1065
raise NotImplementedError(self.dont_leave_lock_in_place)
1066
self._leave_lock = False
1068
def _set_real_repository(self, repository):
1069
"""Set the _real_repository for this repository.
1071
:param repository: The repository to fallback to for non-hpss
1072
implemented operations.
1074
if self._real_repository is not None:
1075
# Replacing an already set real repository.
1076
# We cannot do this [currently] if the repository is locked -
1077
# synchronised state might be lost.
1078
if self.is_locked():
1079
raise AssertionError('_real_repository is already set')
1080
if isinstance(repository, RemoteRepository):
1081
raise AssertionError()
1082
self._real_repository = repository
1083
# three code paths happen here:
1084
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1085
# up stacking. In this case self._fallback_repositories is [], and the
1086
# real repo is already setup. Preserve the real repo and
1087
# RemoteRepository.add_fallback_repository will avoid adding
1089
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1090
# ensure_real is triggered from a branch, the real repository to
1091
# set already has a matching list with separate instances, but
1092
# as they are also RemoteRepositories we don't worry about making the
1093
# lists be identical.
1094
# 3) new servers, RemoteRepository.ensure_real is triggered before
1095
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1096
# and need to populate it.
1097
if (self._fallback_repositories and
1098
len(self._real_repository._fallback_repositories) !=
1099
len(self._fallback_repositories)):
1100
if len(self._real_repository._fallback_repositories):
1101
raise AssertionError(
1102
"cannot cleanly remove existing _fallback_repositories")
1103
for fb in self._fallback_repositories:
1104
self._real_repository.add_fallback_repository(fb)
1105
if self._lock_mode == 'w':
1106
# if we are already locked, the real repository must be able to
1107
# acquire the lock with our token.
1108
self._real_repository.lock_write(self._lock_token)
1109
elif self._lock_mode == 'r':
1110
self._real_repository.lock_read()
1112
def start_write_group(self):
1113
"""Start a write group on the decorated repository.
1115
Smart methods perform operations in a single step so this API
1116
is not really applicable except as a compatibility thunk
1117
for older plugins that don't use e.g. the CommitBuilder
1121
return self._real_repository.start_write_group()
1123
def _unlock(self, token):
1124
path = self.bzrdir._path_for_remote_call(self._client)
1126
# with no token the remote repository is not persistently locked.
1128
err_context = {'token': token}
1129
response = self._call('Repository.unlock', path, token,
1131
if response == ('ok',):
1134
raise errors.UnexpectedSmartServerResponse(response)
1136
@only_raises(errors.LockNotHeld, errors.LockBroken)
1138
if not self._lock_count:
1139
return lock.cant_unlock_not_held(self)
1140
self._lock_count -= 1
1141
if self._lock_count > 0:
1143
self._unstacked_provider.disable_cache()
1144
old_mode = self._lock_mode
1145
self._lock_mode = None
1147
# The real repository is responsible at present for raising an
1148
# exception if it's in an unfinished write group. However, it
1149
# normally will *not* actually remove the lock from disk - that's
1150
# done by the server on receiving the Repository.unlock call.
1151
# This is just to let the _real_repository stay up to date.
1152
if self._real_repository is not None:
1153
self._real_repository.unlock()
1155
# The rpc-level lock should be released even if there was a
1156
# problem releasing the vfs-based lock.
1158
# Only write-locked repositories need to make a remote method
1159
# call to perform the unlock.
1160
old_token = self._lock_token
1161
self._lock_token = None
1162
if not self._leave_lock:
1163
self._unlock(old_token)
1164
# Fallbacks are always 'lock_read()' so we don't pay attention to
1166
for repo in self._fallback_repositories:
1169
def break_lock(self):
1170
# should hand off to the network
1172
return self._real_repository.break_lock()
1174
def _get_tarball(self, compression):
1175
"""Return a TemporaryFile containing a repository tarball.
1177
Returns None if the server does not support sending tarballs.
1180
path = self.bzrdir._path_for_remote_call(self._client)
1182
response, protocol = self._call_expecting_body(
1183
'Repository.tarball', path, compression)
1184
except errors.UnknownSmartMethod:
1185
protocol.cancel_read_body()
1187
if response[0] == 'ok':
1188
# Extract the tarball and return it
1189
t = tempfile.NamedTemporaryFile()
1190
# TODO: rpc layer should read directly into it...
1191
t.write(protocol.read_body_bytes())
1194
raise errors.UnexpectedSmartServerResponse(response)
1196
def sprout(self, to_bzrdir, revision_id=None):
1197
# TODO: Option to control what format is created?
1199
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1201
dest_repo.fetch(self, revision_id=revision_id)
1204
### These methods are just thin shims to the VFS object for now.
1206
def revision_tree(self, revision_id):
1208
return self._real_repository.revision_tree(revision_id)
1210
def get_serializer_format(self):
1212
return self._real_repository.get_serializer_format()
1214
def get_commit_builder(self, branch, parents, config, timestamp=None,
1215
timezone=None, committer=None, revprops=None,
1217
# FIXME: It ought to be possible to call this without immediately
1218
# triggering _ensure_real. For now it's the easiest thing to do.
1220
real_repo = self._real_repository
1221
builder = real_repo.get_commit_builder(branch, parents,
1222
config, timestamp=timestamp, timezone=timezone,
1223
committer=committer, revprops=revprops, revision_id=revision_id)
1226
def add_fallback_repository(self, repository):
1227
"""Add a repository to use for looking up data not held locally.
1229
:param repository: A repository.
1231
if not self._format.supports_external_lookups:
1232
raise errors.UnstackableRepositoryFormat(
1233
self._format.network_name(), self.base)
1234
# We need to accumulate additional repositories here, to pass them in
1237
if self.is_locked():
1238
# We will call fallback.unlock() when we transition to the unlocked
1239
# state, so always add a lock here. If a caller passes us a locked
1240
# repository, they are responsible for unlocking it later.
1241
repository.lock_read()
1242
self._fallback_repositories.append(repository)
1243
# If self._real_repository was parameterised already (e.g. because a
1244
# _real_branch had its get_stacked_on_url method called), then the
1245
# repository to be added may already be in the _real_repositories list.
1246
if self._real_repository is not None:
1247
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1248
self._real_repository._fallback_repositories]
1249
if repository.bzrdir.root_transport.base not in fallback_locations:
1250
self._real_repository.add_fallback_repository(repository)
1252
def add_inventory(self, revid, inv, parents):
1254
return self._real_repository.add_inventory(revid, inv, parents)
1256
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1257
parents, basis_inv=None, propagate_caches=False):
1259
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1260
delta, new_revision_id, parents, basis_inv=basis_inv,
1261
propagate_caches=propagate_caches)
1263
def add_revision(self, rev_id, rev, inv=None, config=None):
1265
return self._real_repository.add_revision(
1266
rev_id, rev, inv=inv, config=config)
1269
def get_inventory(self, revision_id):
1271
return self._real_repository.get_inventory(revision_id)
1273
def iter_inventories(self, revision_ids, ordering=None):
1275
return self._real_repository.iter_inventories(revision_ids, ordering)
1278
def get_revision(self, revision_id):
1280
return self._real_repository.get_revision(revision_id)
1282
def get_transaction(self):
1284
return self._real_repository.get_transaction()
1287
def clone(self, a_bzrdir, revision_id=None):
1289
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1291
def make_working_trees(self):
1292
"""See Repository.make_working_trees"""
1294
return self._real_repository.make_working_trees()
1296
def refresh_data(self):
1297
"""Re-read any data needed to to synchronise with disk.
1299
This method is intended to be called after another repository instance
1300
(such as one used by a smart server) has inserted data into the
1301
repository. It may not be called during a write group, but may be
1302
called at any other time.
1304
if self.is_in_write_group():
1305
raise errors.InternalBzrError(
1306
"May not refresh_data while in a write group.")
1307
if self._real_repository is not None:
1308
self._real_repository.refresh_data()
1310
def revision_ids_to_search_result(self, result_set):
1311
"""Convert a set of revision ids to a graph SearchResult."""
1312
result_parents = set()
1313
for parents in self.get_graph().get_parent_map(
1314
result_set).itervalues():
1315
result_parents.update(parents)
1316
included_keys = result_set.intersection(result_parents)
1317
start_keys = result_set.difference(included_keys)
1318
exclude_keys = result_parents.difference(result_set)
1319
result = graph.SearchResult(start_keys, exclude_keys,
1320
len(result_set), result_set)
1324
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1325
"""Return the revision ids that other has that this does not.
1327
These are returned in topological order.
1329
revision_id: only return revision ids included by revision_id.
1331
return repository.InterRepository.get(
1332
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1334
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1336
# No base implementation to use as RemoteRepository is not a subclass
1337
# of Repository; so this is a copy of Repository.fetch().
1338
if fetch_spec is not None and revision_id is not None:
1339
raise AssertionError(
1340
"fetch_spec and revision_id are mutually exclusive.")
1341
if self.is_in_write_group():
1342
raise errors.InternalBzrError(
1343
"May not fetch while in a write group.")
1344
# fast path same-url fetch operations
1345
if (self.has_same_location(source)
1346
and fetch_spec is None
1347
and self._has_same_fallbacks(source)):
1348
# check that last_revision is in 'from' and then return a
1350
if (revision_id is not None and
1351
not revision.is_null(revision_id)):
1352
self.get_revision(revision_id)
1354
# if there is no specific appropriate InterRepository, this will get
1355
# the InterRepository base class, which raises an
1356
# IncompatibleRepositories when asked to fetch.
1357
inter = repository.InterRepository.get(source, self)
1358
return inter.fetch(revision_id=revision_id, pb=pb,
1359
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1361
def create_bundle(self, target, base, fileobj, format=None):
1363
self._real_repository.create_bundle(target, base, fileobj, format)
1366
def get_ancestry(self, revision_id, topo_sorted=True):
1368
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1370
def fileids_altered_by_revision_ids(self, revision_ids):
1372
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1374
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1376
return self._real_repository._get_versioned_file_checker(
1377
revisions, revision_versions_cache)
1379
def iter_files_bytes(self, desired_files):
1380
"""See Repository.iter_file_bytes.
1383
return self._real_repository.iter_files_bytes(desired_files)
1385
def get_parent_map(self, revision_ids):
1386
"""See bzrlib.Graph.get_parent_map()."""
1387
return self._make_parents_provider().get_parent_map(revision_ids)
1389
def _get_parent_map_rpc(self, keys):
1390
"""Helper for get_parent_map that performs the RPC."""
1391
medium = self._client._medium
1392
if medium._is_remote_before((1, 2)):
1393
# We already found out that the server can't understand
1394
# Repository.get_parent_map requests, so just fetch the whole
1397
# Note that this reads the whole graph, when only some keys are
1398
# wanted. On this old server there's no way (?) to get them all
1399
# in one go, and the user probably will have seen a warning about
1400
# the server being old anyhow.
1401
rg = self._get_revision_graph(None)
1402
# There is an API discrepancy between get_parent_map and
1403
# get_revision_graph. Specifically, a "key:()" pair in
1404
# get_revision_graph just means a node has no parents. For
1405
# "get_parent_map" it means the node is a ghost. So fix up the
1406
# graph to correct this.
1407
# https://bugs.launchpad.net/bzr/+bug/214894
1408
# There is one other "bug" which is that ghosts in
1409
# get_revision_graph() are not returned at all. But we won't worry
1410
# about that for now.
1411
for node_id, parent_ids in rg.iteritems():
1412
if parent_ids == ():
1413
rg[node_id] = (NULL_REVISION,)
1414
rg[NULL_REVISION] = ()
1419
raise ValueError('get_parent_map(None) is not valid')
1420
if NULL_REVISION in keys:
1421
keys.discard(NULL_REVISION)
1422
found_parents = {NULL_REVISION:()}
1424
return found_parents
1427
# TODO(Needs analysis): We could assume that the keys being requested
1428
# from get_parent_map are in a breadth first search, so typically they
1429
# will all be depth N from some common parent, and we don't have to
1430
# have the server iterate from the root parent, but rather from the
1431
# keys we're searching; and just tell the server the keyspace we
1432
# already have; but this may be more traffic again.
1434
# Transform self._parents_map into a search request recipe.
1435
# TODO: Manage this incrementally to avoid covering the same path
1436
# repeatedly. (The server will have to on each request, but the less
1437
# work done the better).
1439
# Negative caching notes:
1440
# new server sends missing when a request including the revid
1441
# 'include-missing:' is present in the request.
1442
# missing keys are serialised as missing:X, and we then call
1443
# provider.note_missing(X) for-all X
1444
parents_map = self._unstacked_provider.get_cached_map()
1445
if parents_map is None:
1446
# Repository is not locked, so there's no cache.
1448
# start_set is all the keys in the cache
1449
start_set = set(parents_map)
1450
# result set is all the references to keys in the cache
1451
result_parents = set()
1452
for parents in parents_map.itervalues():
1453
result_parents.update(parents)
1454
stop_keys = result_parents.difference(start_set)
1455
# We don't need to send ghosts back to the server as a position to
1457
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1458
key_count = len(parents_map)
1459
if (NULL_REVISION in result_parents
1460
and NULL_REVISION in self._unstacked_provider.missing_keys):
1461
# If we pruned NULL_REVISION from the stop_keys because it's also
1462
# in our cache of "missing" keys we need to increment our key count
1463
# by 1, because the reconsitituted SearchResult on the server will
1464
# still consider NULL_REVISION to be an included key.
1466
included_keys = start_set.intersection(result_parents)
1467
start_set.difference_update(included_keys)
1468
recipe = ('manual', start_set, stop_keys, key_count)
1469
body = self._serialise_search_recipe(recipe)
1470
path = self.bzrdir._path_for_remote_call(self._client)
1472
if type(key) is not str:
1474
"key %r not a plain string" % (key,))
1475
verb = 'Repository.get_parent_map'
1476
args = (path, 'include-missing:') + tuple(keys)
1478
response = self._call_with_body_bytes_expecting_body(
1480
except errors.UnknownSmartMethod:
1481
# Server does not support this method, so get the whole graph.
1482
# Worse, we have to force a disconnection, because the server now
1483
# doesn't realise it has a body on the wire to consume, so the
1484
# only way to recover is to abandon the connection.
1486
'Server is too old for fast get_parent_map, reconnecting. '
1487
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1489
# To avoid having to disconnect repeatedly, we keep track of the
1490
# fact the server doesn't understand remote methods added in 1.2.
1491
medium._remember_remote_is_before((1, 2))
1492
# Recurse just once and we should use the fallback code.
1493
return self._get_parent_map_rpc(keys)
1494
response_tuple, response_handler = response
1495
if response_tuple[0] not in ['ok']:
1496
response_handler.cancel_read_body()
1497
raise errors.UnexpectedSmartServerResponse(response_tuple)
1498
if response_tuple[0] == 'ok':
1499
coded = bz2.decompress(response_handler.read_body_bytes())
1501
# no revisions found
1503
lines = coded.split('\n')
1506
d = tuple(line.split())
1508
revision_graph[d[0]] = d[1:]
1511
if d[0].startswith('missing:'):
1513
self._unstacked_provider.note_missing_key(revid)
1515
# no parents - so give the Graph result
1517
revision_graph[d[0]] = (NULL_REVISION,)
1518
return revision_graph
1521
def get_signature_text(self, revision_id):
1523
return self._real_repository.get_signature_text(revision_id)
1526
def _get_inventory_xml(self, revision_id):
1528
return self._real_repository._get_inventory_xml(revision_id)
1530
def reconcile(self, other=None, thorough=False):
1532
return self._real_repository.reconcile(other=other, thorough=thorough)
1534
def all_revision_ids(self):
1536
return self._real_repository.all_revision_ids()
1539
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1541
return self._real_repository.get_deltas_for_revisions(revisions,
1542
specific_fileids=specific_fileids)
1545
def get_revision_delta(self, revision_id, specific_fileids=None):
1547
return self._real_repository.get_revision_delta(revision_id,
1548
specific_fileids=specific_fileids)
1551
def revision_trees(self, revision_ids):
1553
return self._real_repository.revision_trees(revision_ids)
1556
def get_revision_reconcile(self, revision_id):
1558
return self._real_repository.get_revision_reconcile(revision_id)
1561
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1563
return self._real_repository.check(revision_ids=revision_ids,
1564
callback_refs=callback_refs, check_repo=check_repo)
1566
def copy_content_into(self, destination, revision_id=None):
1568
return self._real_repository.copy_content_into(
1569
destination, revision_id=revision_id)
1571
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1572
# get a tarball of the remote repository, and copy from that into the
1574
from bzrlib import osutils
1576
# TODO: Maybe a progress bar while streaming the tarball?
1577
note("Copying repository content as tarball...")
1578
tar_file = self._get_tarball('bz2')
1579
if tar_file is None:
1581
destination = to_bzrdir.create_repository()
1583
tar = tarfile.open('repository', fileobj=tar_file,
1585
tmpdir = osutils.mkdtemp()
1587
_extract_tar(tar, tmpdir)
1588
tmp_bzrdir = BzrDir.open(tmpdir)
1589
tmp_repo = tmp_bzrdir.open_repository()
1590
tmp_repo.copy_content_into(destination, revision_id)
1592
osutils.rmtree(tmpdir)
1596
# TODO: Suggestion from john: using external tar is much faster than
1597
# python's tarfile library, but it may not work on windows.
1600
def inventories(self):
1601
"""Decorate the real repository for now.
1603
In the long term a full blown network facility is needed to
1604
avoid creating a real repository object locally.
1607
return self._real_repository.inventories
1610
def pack(self, hint=None):
1611
"""Compress the data within the repository.
1613
This is not currently implemented within the smart server.
1616
return self._real_repository.pack(hint=hint)
1619
def revisions(self):
1620
"""Decorate the real repository for now.
1622
In the short term this should become a real object to intercept graph
1625
In the long term a full blown network facility is needed.
1628
return self._real_repository.revisions
1630
def set_make_working_trees(self, new_value):
1632
new_value_str = "True"
1634
new_value_str = "False"
1635
path = self.bzrdir._path_for_remote_call(self._client)
1637
response = self._call(
1638
'Repository.set_make_working_trees', path, new_value_str)
1639
except errors.UnknownSmartMethod:
1641
self._real_repository.set_make_working_trees(new_value)
1643
if response[0] != 'ok':
1644
raise errors.UnexpectedSmartServerResponse(response)
1647
def signatures(self):
1648
"""Decorate the real repository for now.
1650
In the long term a full blown network facility is needed to avoid
1651
creating a real repository object locally.
1654
return self._real_repository.signatures
1657
def sign_revision(self, revision_id, gpg_strategy):
1659
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1663
"""Decorate the real repository for now.
1665
In the long term a full blown network facility is needed to avoid
1666
creating a real repository object locally.
1669
return self._real_repository.texts
1672
def get_revisions(self, revision_ids):
1674
return self._real_repository.get_revisions(revision_ids)
1676
def supports_rich_root(self):
1677
return self._format.rich_root_data
1679
def iter_reverse_revision_history(self, revision_id):
1681
return self._real_repository.iter_reverse_revision_history(revision_id)
1684
def _serializer(self):
1685
return self._format._serializer
1687
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1689
return self._real_repository.store_revision_signature(
1690
gpg_strategy, plaintext, revision_id)
1692
def add_signature_text(self, revision_id, signature):
1694
return self._real_repository.add_signature_text(revision_id, signature)
1696
def has_signature_for_revision_id(self, revision_id):
1698
return self._real_repository.has_signature_for_revision_id(revision_id)
1700
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1702
return self._real_repository.item_keys_introduced_by(revision_ids,
1703
_files_pb=_files_pb)
1705
def revision_graph_can_have_wrong_parents(self):
1706
# The answer depends on the remote repo format.
1708
return self._real_repository.revision_graph_can_have_wrong_parents()
1710
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1712
return self._real_repository._find_inconsistent_revision_parents(
1715
def _check_for_inconsistent_revision_parents(self):
1717
return self._real_repository._check_for_inconsistent_revision_parents()
1719
def _make_parents_provider(self, other=None):
1720
providers = [self._unstacked_provider]
1721
if other is not None:
1722
providers.insert(0, other)
1723
providers.extend(r._make_parents_provider() for r in
1724
self._fallback_repositories)
1725
return graph.StackedParentsProvider(providers)
1727
def _serialise_search_recipe(self, recipe):
1728
"""Serialise a graph search recipe.
1730
:param recipe: A search recipe (start, stop, count).
1731
:return: Serialised bytes.
1733
start_keys = ' '.join(recipe[1])
1734
stop_keys = ' '.join(recipe[2])
1735
count = str(recipe[3])
1736
return '\n'.join((start_keys, stop_keys, count))
1738
def _serialise_search_result(self, search_result):
1739
if isinstance(search_result, graph.PendingAncestryResult):
1740
parts = ['ancestry-of']
1741
parts.extend(search_result.heads)
1743
recipe = search_result.get_recipe()
1744
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1745
return '\n'.join(parts)
1748
path = self.bzrdir._path_for_remote_call(self._client)
1750
response = self._call('PackRepository.autopack', path)
1751
except errors.UnknownSmartMethod:
1753
self._real_repository._pack_collection.autopack()
1756
if response[0] != 'ok':
1757
raise errors.UnexpectedSmartServerResponse(response)
1760
class RemoteStreamSink(repository.StreamSink):
1762
def _insert_real(self, stream, src_format, resume_tokens):
1763
self.target_repo._ensure_real()
1764
sink = self.target_repo._real_repository._get_sink()
1765
result = sink.insert_stream(stream, src_format, resume_tokens)
1767
self.target_repo.autopack()
1770
def insert_stream(self, stream, src_format, resume_tokens):
1771
target = self.target_repo
1772
target._unstacked_provider.missing_keys.clear()
1773
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1774
if target._lock_token:
1775
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1776
lock_args = (target._lock_token or '',)
1778
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1780
client = target._client
1781
medium = client._medium
1782
path = target.bzrdir._path_for_remote_call(client)
1783
# Probe for the verb to use with an empty stream before sending the
1784
# real stream to it. We do this both to avoid the risk of sending a
1785
# large request that is then rejected, and because we don't want to
1786
# implement a way to buffer, rewind, or restart the stream.
1788
for verb, required_version in candidate_calls:
1789
if medium._is_remote_before(required_version):
1792
# We've already done the probing (and set _is_remote_before) on
1793
# a previous insert.
1796
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1798
response = client.call_with_body_stream(
1799
(verb, path, '') + lock_args, byte_stream)
1800
except errors.UnknownSmartMethod:
1801
medium._remember_remote_is_before(required_version)
1807
return self._insert_real(stream, src_format, resume_tokens)
1808
self._last_inv_record = None
1809
self._last_substream = None
1810
if required_version < (1, 19):
1811
# Remote side doesn't support inventory deltas. Wrap the stream to
1812
# make sure we don't send any. If the stream contains inventory
1813
# deltas we'll interrupt the smart insert_stream request and
1815
stream = self._stop_stream_if_inventory_delta(stream)
1816
byte_stream = smart_repo._stream_to_byte_stream(
1818
resume_tokens = ' '.join(resume_tokens)
1819
response = client.call_with_body_stream(
1820
(verb, path, resume_tokens) + lock_args, byte_stream)
1821
if response[0][0] not in ('ok', 'missing-basis'):
1822
raise errors.UnexpectedSmartServerResponse(response)
1823
if self._last_substream is not None:
1824
# The stream included an inventory-delta record, but the remote
1825
# side isn't new enough to support them. So we need to send the
1826
# rest of the stream via VFS.
1827
self.target_repo.refresh_data()
1828
return self._resume_stream_with_vfs(response, src_format)
1829
if response[0][0] == 'missing-basis':
1830
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1831
resume_tokens = tokens
1832
return resume_tokens, set(missing_keys)
1834
self.target_repo.refresh_data()
1837
def _resume_stream_with_vfs(self, response, src_format):
1838
"""Resume sending a stream via VFS, first resending the record and
1839
substream that couldn't be sent via an insert_stream verb.
1841
if response[0][0] == 'missing-basis':
1842
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1843
# Ignore missing_keys, we haven't finished inserting yet
1846
def resume_substream():
1847
# Yield the substream that was interrupted.
1848
for record in self._last_substream:
1850
self._last_substream = None
1851
def resume_stream():
1852
# Finish sending the interrupted substream
1853
yield ('inventory-deltas', resume_substream())
1854
# Then simply continue sending the rest of the stream.
1855
for substream_kind, substream in self._last_stream:
1856
yield substream_kind, substream
1857
return self._insert_real(resume_stream(), src_format, tokens)
1859
def _stop_stream_if_inventory_delta(self, stream):
1860
"""Normally this just lets the original stream pass-through unchanged.
1862
However if any 'inventory-deltas' substream occurs it will stop
1863
streaming, and store the interrupted substream and stream in
1864
self._last_substream and self._last_stream so that the stream can be
1865
resumed by _resume_stream_with_vfs.
1868
stream_iter = iter(stream)
1869
for substream_kind, substream in stream_iter:
1870
if substream_kind == 'inventory-deltas':
1871
self._last_substream = substream
1872
self._last_stream = stream_iter
1875
yield substream_kind, substream
1878
class RemoteStreamSource(repository.StreamSource):
1879
"""Stream data from a remote server."""
1881
def get_stream(self, search):
1882
if (self.from_repository._fallback_repositories and
1883
self.to_format._fetch_order == 'topological'):
1884
return self._real_stream(self.from_repository, search)
1887
repos = [self.from_repository]
1893
repos.extend(repo._fallback_repositories)
1894
sources.append(repo)
1895
return self.missing_parents_chain(search, sources)
1897
def get_stream_for_missing_keys(self, missing_keys):
1898
self.from_repository._ensure_real()
1899
real_repo = self.from_repository._real_repository
1900
real_source = real_repo._get_source(self.to_format)
1901
return real_source.get_stream_for_missing_keys(missing_keys)
1903
def _real_stream(self, repo, search):
1904
"""Get a stream for search from repo.
1906
This never called RemoteStreamSource.get_stream, and is a heler
1907
for RemoteStreamSource._get_stream to allow getting a stream
1908
reliably whether fallback back because of old servers or trying
1909
to stream from a non-RemoteRepository (which the stacked support
1912
source = repo._get_source(self.to_format)
1913
if isinstance(source, RemoteStreamSource):
1915
source = repo._real_repository._get_source(self.to_format)
1916
return source.get_stream(search)
1918
def _get_stream(self, repo, search):
1919
"""Core worker to get a stream from repo for search.
1921
This is used by both get_stream and the stacking support logic. It
1922
deliberately gets a stream for repo which does not need to be
1923
self.from_repository. In the event that repo is not Remote, or
1924
cannot do a smart stream, a fallback is made to the generic
1925
repository._get_stream() interface, via self._real_stream.
1927
In the event of stacking, streams from _get_stream will not
1928
contain all the data for search - this is normal (see get_stream).
1930
:param repo: A repository.
1931
:param search: A search.
1933
# Fallbacks may be non-smart
1934
if not isinstance(repo, RemoteRepository):
1935
return self._real_stream(repo, search)
1936
client = repo._client
1937
medium = client._medium
1938
path = repo.bzrdir._path_for_remote_call(client)
1939
search_bytes = repo._serialise_search_result(search)
1940
args = (path, self.to_format.network_name())
1942
('Repository.get_stream_1.19', (1, 19)),
1943
('Repository.get_stream', (1, 13))]
1945
for verb, version in candidate_verbs:
1946
if medium._is_remote_before(version):
1949
response = repo._call_with_body_bytes_expecting_body(
1950
verb, args, search_bytes)
1951
except errors.UnknownSmartMethod:
1952
medium._remember_remote_is_before(version)
1954
response_tuple, response_handler = response
1958
return self._real_stream(repo, search)
1959
if response_tuple[0] != 'ok':
1960
raise errors.UnexpectedSmartServerResponse(response_tuple)
1961
byte_stream = response_handler.read_streamed_body()
1962
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1963
if src_format.network_name() != repo._format.network_name():
1964
raise AssertionError(
1965
"Mismatched RemoteRepository and stream src %r, %r" % (
1966
src_format.network_name(), repo._format.network_name()))
1969
def missing_parents_chain(self, search, sources):
1970
"""Chain multiple streams together to handle stacking.
1972
:param search: The overall search to satisfy with streams.
1973
:param sources: A list of Repository objects to query.
1975
self.from_serialiser = self.from_repository._format._serializer
1976
self.seen_revs = set()
1977
self.referenced_revs = set()
1978
# If there are heads in the search, or the key count is > 0, we are not
1980
while not search.is_empty() and len(sources) > 1:
1981
source = sources.pop(0)
1982
stream = self._get_stream(source, search)
1983
for kind, substream in stream:
1984
if kind != 'revisions':
1985
yield kind, substream
1987
yield kind, self.missing_parents_rev_handler(substream)
1988
search = search.refine(self.seen_revs, self.referenced_revs)
1989
self.seen_revs = set()
1990
self.referenced_revs = set()
1991
if not search.is_empty():
1992
for kind, stream in self._get_stream(sources[0], search):
1995
def missing_parents_rev_handler(self, substream):
1996
for content in substream:
1997
revision_bytes = content.get_bytes_as('fulltext')
1998
revision = self.from_serialiser.read_revision_from_string(
2000
self.seen_revs.add(content.key[-1])
2001
self.referenced_revs.update(revision.parent_ids)
2005
class RemoteBranchLockableFiles(LockableFiles):
2006
"""A 'LockableFiles' implementation that talks to a smart server.
2008
This is not a public interface class.
2011
def __init__(self, bzrdir, _client):
2012
self.bzrdir = bzrdir
2013
self._client = _client
2014
self._need_find_modes = True
2015
LockableFiles.__init__(
2016
self, bzrdir.get_branch_transport(None),
2017
'lock', lockdir.LockDir)
2019
def _find_modes(self):
2020
# RemoteBranches don't let the client set the mode of control files.
2021
self._dir_mode = None
2022
self._file_mode = None
2025
class RemoteBranchFormat(branch.BranchFormat):
2027
def __init__(self, network_name=None):
2028
super(RemoteBranchFormat, self).__init__()
2029
self._matchingbzrdir = RemoteBzrDirFormat()
2030
self._matchingbzrdir.set_branch_format(self)
2031
self._custom_format = None
2032
self._network_name = network_name
2034
def __eq__(self, other):
2035
return (isinstance(other, RemoteBranchFormat) and
2036
self.__dict__ == other.__dict__)
2038
def _ensure_real(self):
2039
if self._custom_format is None:
2040
self._custom_format = branch.network_format_registry.get(
2043
def get_format_description(self):
2045
return 'Remote: ' + self._custom_format.get_format_description()
2047
def network_name(self):
2048
return self._network_name
2050
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2051
return a_bzrdir.open_branch(name=name,
2052
ignore_fallbacks=ignore_fallbacks)
2054
def _vfs_initialize(self, a_bzrdir, name):
2055
# Initialisation when using a local bzrdir object, or a non-vfs init
2056
# method is not available on the server.
2057
# self._custom_format is always set - the start of initialize ensures
2059
if isinstance(a_bzrdir, RemoteBzrDir):
2060
a_bzrdir._ensure_real()
2061
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2064
# We assume the bzrdir is parameterised; it may not be.
2065
result = self._custom_format.initialize(a_bzrdir, name)
2066
if (isinstance(a_bzrdir, RemoteBzrDir) and
2067
not isinstance(result, RemoteBranch)):
2068
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2072
def initialize(self, a_bzrdir, name=None):
2073
# 1) get the network name to use.
2074
if self._custom_format:
2075
network_name = self._custom_format.network_name()
2077
# Select the current bzrlib default and ask for that.
2078
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2079
reference_format = reference_bzrdir_format.get_branch_format()
2080
self._custom_format = reference_format
2081
network_name = reference_format.network_name()
2082
# Being asked to create on a non RemoteBzrDir:
2083
if not isinstance(a_bzrdir, RemoteBzrDir):
2084
return self._vfs_initialize(a_bzrdir, name=name)
2085
medium = a_bzrdir._client._medium
2086
if medium._is_remote_before((1, 13)):
2087
return self._vfs_initialize(a_bzrdir, name=name)
2088
# Creating on a remote bzr dir.
2089
# 2) try direct creation via RPC
2090
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2091
if name is not None:
2092
# XXX JRV20100304: Support creating colocated branches
2093
raise errors.NoColocatedBranchSupport(self)
2094
verb = 'BzrDir.create_branch'
2096
response = a_bzrdir._call(verb, path, network_name)
2097
except errors.UnknownSmartMethod:
2098
# Fallback - use vfs methods
2099
medium._remember_remote_is_before((1, 13))
2100
return self._vfs_initialize(a_bzrdir, name=name)
2101
if response[0] != 'ok':
2102
raise errors.UnexpectedSmartServerResponse(response)
2103
# Turn the response into a RemoteRepository object.
2104
format = RemoteBranchFormat(network_name=response[1])
2105
repo_format = response_tuple_to_repo_format(response[3:])
2106
if response[2] == '':
2107
repo_bzrdir = a_bzrdir
2109
repo_bzrdir = RemoteBzrDir(
2110
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2112
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2113
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2114
format=format, setup_stacking=False, name=name)
2115
# XXX: We know this is a new branch, so it must have revno 0, revid
2116
# NULL_REVISION. Creating the branch locked would make this be unable
2117
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2118
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2119
return remote_branch
2121
def make_tags(self, branch):
2123
return self._custom_format.make_tags(branch)
2125
def supports_tags(self):
2126
# Remote branches might support tags, but we won't know until we
2127
# access the real remote branch.
2129
return self._custom_format.supports_tags()
2131
def supports_stacking(self):
2133
return self._custom_format.supports_stacking()
2135
def supports_set_append_revisions_only(self):
2137
return self._custom_format.supports_set_append_revisions_only()
2140
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2141
"""Branch stored on a server accessed by HPSS RPC.
2143
At the moment most operations are mapped down to simple file operations.
2146
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2147
_client=None, format=None, setup_stacking=True, name=None):
2148
"""Create a RemoteBranch instance.
2150
:param real_branch: An optional local implementation of the branch
2151
format, usually accessing the data via the VFS.
2152
:param _client: Private parameter for testing.
2153
:param format: A RemoteBranchFormat object, None to create one
2154
automatically. If supplied it should have a network_name already
2156
:param setup_stacking: If True make an RPC call to determine the
2157
stacked (or not) status of the branch. If False assume the branch
2159
:param name: Colocated branch name
2161
# We intentionally don't call the parent class's __init__, because it
2162
# will try to assign to self.tags, which is a property in this subclass.
2163
# And the parent's __init__ doesn't do much anyway.
2164
self.bzrdir = remote_bzrdir
2165
if _client is not None:
2166
self._client = _client
2168
self._client = remote_bzrdir._client
2169
self.repository = remote_repository
2170
if real_branch is not None:
2171
self._real_branch = real_branch
2172
# Give the remote repository the matching real repo.
2173
real_repo = self._real_branch.repository
2174
if isinstance(real_repo, RemoteRepository):
2175
real_repo._ensure_real()
2176
real_repo = real_repo._real_repository
2177
self.repository._set_real_repository(real_repo)
2178
# Give the branch the remote repository to let fast-pathing happen.
2179
self._real_branch.repository = self.repository
2181
self._real_branch = None
2182
# Fill out expected attributes of branch for bzrlib API users.
2183
self._clear_cached_state()
2184
self.base = self.bzrdir.root_transport.base
2186
self._control_files = None
2187
self._lock_mode = None
2188
self._lock_token = None
2189
self._repo_lock_token = None
2190
self._lock_count = 0
2191
self._leave_lock = False
2192
# Setup a format: note that we cannot call _ensure_real until all the
2193
# attributes above are set: This code cannot be moved higher up in this
2196
self._format = RemoteBranchFormat()
2197
if real_branch is not None:
2198
self._format._network_name = \
2199
self._real_branch._format.network_name()
2201
self._format = format
2202
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2203
# branch.open_branch method.
2204
self._real_ignore_fallbacks = not setup_stacking
2205
if not self._format._network_name:
2206
# Did not get from open_branchV2 - old server.
2208
self._format._network_name = \
2209
self._real_branch._format.network_name()
2210
self.tags = self._format.make_tags(self)
2211
# The base class init is not called, so we duplicate this:
2212
hooks = branch.Branch.hooks['open']
2215
self._is_stacked = False
2217
self._setup_stacking()
2219
def _setup_stacking(self):
2220
# configure stacking into the remote repository, by reading it from
2223
fallback_url = self.get_stacked_on_url()
2224
except (errors.NotStacked, errors.UnstackableBranchFormat,
2225
errors.UnstackableRepositoryFormat), e:
2227
self._is_stacked = True
2228
self._activate_fallback_location(fallback_url)
2230
def _get_config(self):
2231
return RemoteBranchConfig(self)
2233
def _get_real_transport(self):
2234
# if we try vfs access, return the real branch's vfs transport
2236
return self._real_branch._transport
2238
_transport = property(_get_real_transport)
2241
return "%s(%s)" % (self.__class__.__name__, self.base)
2245
def _ensure_real(self):
2246
"""Ensure that there is a _real_branch set.
2248
Used before calls to self._real_branch.
2250
if self._real_branch is None:
2251
if not vfs.vfs_enabled():
2252
raise AssertionError('smart server vfs must be enabled '
2253
'to use vfs implementation')
2254
self.bzrdir._ensure_real()
2255
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2256
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2257
if self.repository._real_repository is None:
2258
# Give the remote repository the matching real repo.
2259
real_repo = self._real_branch.repository
2260
if isinstance(real_repo, RemoteRepository):
2261
real_repo._ensure_real()
2262
real_repo = real_repo._real_repository
2263
self.repository._set_real_repository(real_repo)
2264
# Give the real branch the remote repository to let fast-pathing
2266
self._real_branch.repository = self.repository
2267
if self._lock_mode == 'r':
2268
self._real_branch.lock_read()
2269
elif self._lock_mode == 'w':
2270
self._real_branch.lock_write(token=self._lock_token)
2272
def _translate_error(self, err, **context):
2273
self.repository._translate_error(err, branch=self, **context)
2275
def _clear_cached_state(self):
2276
super(RemoteBranch, self)._clear_cached_state()
2277
if self._real_branch is not None:
2278
self._real_branch._clear_cached_state()
2280
def _clear_cached_state_of_remote_branch_only(self):
2281
"""Like _clear_cached_state, but doesn't clear the cache of
2284
This is useful when falling back to calling a method of
2285
self._real_branch that changes state. In that case the underlying
2286
branch changes, so we need to invalidate this RemoteBranch's cache of
2287
it. However, there's no need to invalidate the _real_branch's cache
2288
too, in fact doing so might harm performance.
2290
super(RemoteBranch, self)._clear_cached_state()
2293
def control_files(self):
2294
# Defer actually creating RemoteBranchLockableFiles until its needed,
2295
# because it triggers an _ensure_real that we otherwise might not need.
2296
if self._control_files is None:
2297
self._control_files = RemoteBranchLockableFiles(
2298
self.bzrdir, self._client)
2299
return self._control_files
2301
def _get_checkout_format(self):
2303
return self._real_branch._get_checkout_format()
2305
def get_physical_lock_status(self):
2306
"""See Branch.get_physical_lock_status()."""
2307
# should be an API call to the server, as branches must be lockable.
2309
return self._real_branch.get_physical_lock_status()
2311
def get_stacked_on_url(self):
2312
"""Get the URL this branch is stacked against.
2314
:raises NotStacked: If the branch is not stacked.
2315
:raises UnstackableBranchFormat: If the branch does not support
2317
:raises UnstackableRepositoryFormat: If the repository does not support
2321
# there may not be a repository yet, so we can't use
2322
# self._translate_error, so we can't use self._call either.
2323
response = self._client.call('Branch.get_stacked_on_url',
2324
self._remote_path())
2325
except errors.ErrorFromSmartServer, err:
2326
# there may not be a repository yet, so we can't call through
2327
# its _translate_error
2328
_translate_error(err, branch=self)
2329
except errors.UnknownSmartMethod, err:
2331
return self._real_branch.get_stacked_on_url()
2332
if response[0] != 'ok':
2333
raise errors.UnexpectedSmartServerResponse(response)
2336
def set_stacked_on_url(self, url):
2337
branch.Branch.set_stacked_on_url(self, url)
2339
self._is_stacked = False
2341
self._is_stacked = True
2343
def _vfs_get_tags_bytes(self):
2345
return self._real_branch._get_tags_bytes()
2347
def _get_tags_bytes(self):
2348
medium = self._client._medium
2349
if medium._is_remote_before((1, 13)):
2350
return self._vfs_get_tags_bytes()
2352
response = self._call('Branch.get_tags_bytes', self._remote_path())
2353
except errors.UnknownSmartMethod:
2354
medium._remember_remote_is_before((1, 13))
2355
return self._vfs_get_tags_bytes()
2358
def _vfs_set_tags_bytes(self, bytes):
2360
return self._real_branch._set_tags_bytes(bytes)
2362
def _set_tags_bytes(self, bytes):
2363
medium = self._client._medium
2364
if medium._is_remote_before((1, 18)):
2365
self._vfs_set_tags_bytes(bytes)
2369
self._remote_path(), self._lock_token, self._repo_lock_token)
2370
response = self._call_with_body_bytes(
2371
'Branch.set_tags_bytes', args, bytes)
2372
except errors.UnknownSmartMethod:
2373
medium._remember_remote_is_before((1, 18))
2374
self._vfs_set_tags_bytes(bytes)
2376
def lock_read(self):
2377
self.repository.lock_read()
2378
if not self._lock_mode:
2379
self._note_lock('r')
2380
self._lock_mode = 'r'
2381
self._lock_count = 1
2382
if self._real_branch is not None:
2383
self._real_branch.lock_read()
2385
self._lock_count += 1
2387
def _remote_lock_write(self, token):
2389
branch_token = repo_token = ''
2391
branch_token = token
2392
repo_token = self.repository.lock_write()
2393
self.repository.unlock()
2394
err_context = {'token': token}
2395
response = self._call(
2396
'Branch.lock_write', self._remote_path(), branch_token,
2397
repo_token or '', **err_context)
2398
if response[0] != 'ok':
2399
raise errors.UnexpectedSmartServerResponse(response)
2400
ok, branch_token, repo_token = response
2401
return branch_token, repo_token
2403
def lock_write(self, token=None):
2404
if not self._lock_mode:
2405
self._note_lock('w')
2406
# Lock the branch and repo in one remote call.
2407
remote_tokens = self._remote_lock_write(token)
2408
self._lock_token, self._repo_lock_token = remote_tokens
2409
if not self._lock_token:
2410
raise SmartProtocolError('Remote server did not return a token!')
2411
# Tell the self.repository object that it is locked.
2412
self.repository.lock_write(
2413
self._repo_lock_token, _skip_rpc=True)
2415
if self._real_branch is not None:
2416
self._real_branch.lock_write(token=self._lock_token)
2417
if token is not None:
2418
self._leave_lock = True
2420
self._leave_lock = False
2421
self._lock_mode = 'w'
2422
self._lock_count = 1
2423
elif self._lock_mode == 'r':
2424
raise errors.ReadOnlyTransaction
2426
if token is not None:
2427
# A token was given to lock_write, and we're relocking, so
2428
# check that the given token actually matches the one we
2430
if token != self._lock_token:
2431
raise errors.TokenMismatch(token, self._lock_token)
2432
self._lock_count += 1
2433
# Re-lock the repository too.
2434
self.repository.lock_write(self._repo_lock_token)
2435
return self._lock_token or None
2437
def _unlock(self, branch_token, repo_token):
2438
err_context = {'token': str((branch_token, repo_token))}
2439
response = self._call(
2440
'Branch.unlock', self._remote_path(), branch_token,
2441
repo_token or '', **err_context)
2442
if response == ('ok',):
2444
raise errors.UnexpectedSmartServerResponse(response)
2446
@only_raises(errors.LockNotHeld, errors.LockBroken)
2449
self._lock_count -= 1
2450
if not self._lock_count:
2451
self._clear_cached_state()
2452
mode = self._lock_mode
2453
self._lock_mode = None
2454
if self._real_branch is not None:
2455
if (not self._leave_lock and mode == 'w' and
2456
self._repo_lock_token):
2457
# If this RemoteBranch will remove the physical lock
2458
# for the repository, make sure the _real_branch
2459
# doesn't do it first. (Because the _real_branch's
2460
# repository is set to be the RemoteRepository.)
2461
self._real_branch.repository.leave_lock_in_place()
2462
self._real_branch.unlock()
2464
# Only write-locked branched need to make a remote method
2465
# call to perform the unlock.
2467
if not self._lock_token:
2468
raise AssertionError('Locked, but no token!')
2469
branch_token = self._lock_token
2470
repo_token = self._repo_lock_token
2471
self._lock_token = None
2472
self._repo_lock_token = None
2473
if not self._leave_lock:
2474
self._unlock(branch_token, repo_token)
2476
self.repository.unlock()
2478
def break_lock(self):
2480
return self._real_branch.break_lock()
2482
def leave_lock_in_place(self):
2483
if not self._lock_token:
2484
raise NotImplementedError(self.leave_lock_in_place)
2485
self._leave_lock = True
2487
def dont_leave_lock_in_place(self):
2488
if not self._lock_token:
2489
raise NotImplementedError(self.dont_leave_lock_in_place)
2490
self._leave_lock = False
2493
def get_rev_id(self, revno, history=None):
2495
return _mod_revision.NULL_REVISION
2496
last_revision_info = self.last_revision_info()
2497
ok, result = self.repository.get_rev_id_for_revno(
2498
revno, last_revision_info)
2501
missing_parent = result[1]
2502
# Either the revision named by the server is missing, or its parent
2503
# is. Call get_parent_map to determine which, so that we report a
2505
parent_map = self.repository.get_parent_map([missing_parent])
2506
if missing_parent in parent_map:
2507
missing_parent = parent_map[missing_parent]
2508
raise errors.RevisionNotPresent(missing_parent, self.repository)
2510
def _last_revision_info(self):
2511
response = self._call('Branch.last_revision_info', self._remote_path())
2512
if response[0] != 'ok':
2513
raise SmartProtocolError('unexpected response code %s' % (response,))
2514
revno = int(response[1])
2515
last_revision = response[2]
2516
return (revno, last_revision)
2518
def _gen_revision_history(self):
2519
"""See Branch._gen_revision_history()."""
2520
if self._is_stacked:
2522
return self._real_branch._gen_revision_history()
2523
response_tuple, response_handler = self._call_expecting_body(
2524
'Branch.revision_history', self._remote_path())
2525
if response_tuple[0] != 'ok':
2526
raise errors.UnexpectedSmartServerResponse(response_tuple)
2527
result = response_handler.read_body_bytes().split('\x00')
2532
def _remote_path(self):
2533
return self.bzrdir._path_for_remote_call(self._client)
2535
def _set_last_revision_descendant(self, revision_id, other_branch,
2536
allow_diverged=False, allow_overwrite_descendant=False):
2537
# This performs additional work to meet the hook contract; while its
2538
# undesirable, we have to synthesise the revno to call the hook, and
2539
# not calling the hook is worse as it means changes can't be prevented.
2540
# Having calculated this though, we can't just call into
2541
# set_last_revision_info as a simple call, because there is a set_rh
2542
# hook that some folk may still be using.
2543
old_revno, old_revid = self.last_revision_info()
2544
history = self._lefthand_history(revision_id)
2545
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2546
err_context = {'other_branch': other_branch}
2547
response = self._call('Branch.set_last_revision_ex',
2548
self._remote_path(), self._lock_token, self._repo_lock_token,
2549
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2551
self._clear_cached_state()
2552
if len(response) != 3 and response[0] != 'ok':
2553
raise errors.UnexpectedSmartServerResponse(response)
2554
new_revno, new_revision_id = response[1:]
2555
self._last_revision_info_cache = new_revno, new_revision_id
2556
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2557
if self._real_branch is not None:
2558
cache = new_revno, new_revision_id
2559
self._real_branch._last_revision_info_cache = cache
2561
def _set_last_revision(self, revision_id):
2562
old_revno, old_revid = self.last_revision_info()
2563
# This performs additional work to meet the hook contract; while its
2564
# undesirable, we have to synthesise the revno to call the hook, and
2565
# not calling the hook is worse as it means changes can't be prevented.
2566
# Having calculated this though, we can't just call into
2567
# set_last_revision_info as a simple call, because there is a set_rh
2568
# hook that some folk may still be using.
2569
history = self._lefthand_history(revision_id)
2570
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2571
self._clear_cached_state()
2572
response = self._call('Branch.set_last_revision',
2573
self._remote_path(), self._lock_token, self._repo_lock_token,
2575
if response != ('ok',):
2576
raise errors.UnexpectedSmartServerResponse(response)
2577
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2580
def set_revision_history(self, rev_history):
2581
# Send just the tip revision of the history; the server will generate
2582
# the full history from that. If the revision doesn't exist in this
2583
# branch, NoSuchRevision will be raised.
2584
if rev_history == []:
2587
rev_id = rev_history[-1]
2588
self._set_last_revision(rev_id)
2589
for hook in branch.Branch.hooks['set_rh']:
2590
hook(self, rev_history)
2591
self._cache_revision_history(rev_history)
2593
def _get_parent_location(self):
2594
medium = self._client._medium
2595
if medium._is_remote_before((1, 13)):
2596
return self._vfs_get_parent_location()
2598
response = self._call('Branch.get_parent', self._remote_path())
2599
except errors.UnknownSmartMethod:
2600
medium._remember_remote_is_before((1, 13))
2601
return self._vfs_get_parent_location()
2602
if len(response) != 1:
2603
raise errors.UnexpectedSmartServerResponse(response)
2604
parent_location = response[0]
2605
if parent_location == '':
2607
return parent_location
2609
def _vfs_get_parent_location(self):
2611
return self._real_branch._get_parent_location()
2613
def _set_parent_location(self, url):
2614
medium = self._client._medium
2615
if medium._is_remote_before((1, 15)):
2616
return self._vfs_set_parent_location(url)
2618
call_url = url or ''
2619
if type(call_url) is not str:
2620
raise AssertionError('url must be a str or None (%s)' % url)
2621
response = self._call('Branch.set_parent_location',
2622
self._remote_path(), self._lock_token, self._repo_lock_token,
2624
except errors.UnknownSmartMethod:
2625
medium._remember_remote_is_before((1, 15))
2626
return self._vfs_set_parent_location(url)
2628
raise errors.UnexpectedSmartServerResponse(response)
2630
def _vfs_set_parent_location(self, url):
2632
return self._real_branch._set_parent_location(url)
2635
def pull(self, source, overwrite=False, stop_revision=None,
2637
self._clear_cached_state_of_remote_branch_only()
2639
return self._real_branch.pull(
2640
source, overwrite=overwrite, stop_revision=stop_revision,
2641
_override_hook_target=self, **kwargs)
2644
def push(self, target, overwrite=False, stop_revision=None):
2646
return self._real_branch.push(
2647
target, overwrite=overwrite, stop_revision=stop_revision,
2648
_override_hook_source_branch=self)
2650
def is_locked(self):
2651
return self._lock_count >= 1
2654
def revision_id_to_revno(self, revision_id):
2656
return self._real_branch.revision_id_to_revno(revision_id)
2659
def set_last_revision_info(self, revno, revision_id):
2660
# XXX: These should be returned by the set_last_revision_info verb
2661
old_revno, old_revid = self.last_revision_info()
2662
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2663
revision_id = ensure_null(revision_id)
2665
response = self._call('Branch.set_last_revision_info',
2666
self._remote_path(), self._lock_token, self._repo_lock_token,
2667
str(revno), revision_id)
2668
except errors.UnknownSmartMethod:
2670
self._clear_cached_state_of_remote_branch_only()
2671
self._real_branch.set_last_revision_info(revno, revision_id)
2672
self._last_revision_info_cache = revno, revision_id
2674
if response == ('ok',):
2675
self._clear_cached_state()
2676
self._last_revision_info_cache = revno, revision_id
2677
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2678
# Update the _real_branch's cache too.
2679
if self._real_branch is not None:
2680
cache = self._last_revision_info_cache
2681
self._real_branch._last_revision_info_cache = cache
2683
raise errors.UnexpectedSmartServerResponse(response)
2686
def generate_revision_history(self, revision_id, last_rev=None,
2688
medium = self._client._medium
2689
if not medium._is_remote_before((1, 6)):
2690
# Use a smart method for 1.6 and above servers
2692
self._set_last_revision_descendant(revision_id, other_branch,
2693
allow_diverged=True, allow_overwrite_descendant=True)
2695
except errors.UnknownSmartMethod:
2696
medium._remember_remote_is_before((1, 6))
2697
self._clear_cached_state_of_remote_branch_only()
2698
self.set_revision_history(self._lefthand_history(revision_id,
2699
last_rev=last_rev,other_branch=other_branch))
2701
def set_push_location(self, location):
2703
return self._real_branch.set_push_location(location)
2706
class RemoteConfig(object):
2707
"""A Config that reads and writes from smart verbs.
2709
It is a low-level object that considers config data to be name/value pairs
2710
that may be associated with a section. Assigning meaning to the these
2711
values is done at higher levels like bzrlib.config.TreeConfig.
2714
def get_option(self, name, section=None, default=None):
2715
"""Return the value associated with a named option.
2717
:param name: The name of the value
2718
:param section: The section the option is in (if any)
2719
:param default: The value to return if the value is not set
2720
:return: The value or default value
2723
configobj = self._get_configobj()
2725
section_obj = configobj
2728
section_obj = configobj[section]
2731
return section_obj.get(name, default)
2732
except errors.UnknownSmartMethod:
2733
return self._vfs_get_option(name, section, default)
2735
def _response_to_configobj(self, response):
2736
if len(response[0]) and response[0][0] != 'ok':
2737
raise errors.UnexpectedSmartServerResponse(response)
2738
lines = response[1].read_body_bytes().splitlines()
2739
return config.ConfigObj(lines, encoding='utf-8')
2742
class RemoteBranchConfig(RemoteConfig):
2743
"""A RemoteConfig for Branches."""
2745
def __init__(self, branch):
2746
self._branch = branch
2748
def _get_configobj(self):
2749
path = self._branch._remote_path()
2750
response = self._branch._client.call_expecting_body(
2751
'Branch.get_config_file', path)
2752
return self._response_to_configobj(response)
2754
def set_option(self, value, name, section=None):
2755
"""Set the value associated with a named option.
2757
:param value: The value to set
2758
:param name: The name of the value to set
2759
:param section: The section the option is in (if any)
2761
medium = self._branch._client._medium
2762
if medium._is_remote_before((1, 14)):
2763
return self._vfs_set_option(value, name, section)
2765
path = self._branch._remote_path()
2766
response = self._branch._client.call('Branch.set_config_option',
2767
path, self._branch._lock_token, self._branch._repo_lock_token,
2768
value.encode('utf8'), name, section or '')
2769
except errors.UnknownSmartMethod:
2770
medium._remember_remote_is_before((1, 14))
2771
return self._vfs_set_option(value, name, section)
2773
raise errors.UnexpectedSmartServerResponse(response)
2775
def _real_object(self):
2776
self._branch._ensure_real()
2777
return self._branch._real_branch
2779
def _vfs_set_option(self, value, name, section=None):
2780
return self._real_object()._get_config().set_option(
2781
value, name, section)
2784
class RemoteBzrDirConfig(RemoteConfig):
2785
"""A RemoteConfig for BzrDirs."""
2787
def __init__(self, bzrdir):
2788
self._bzrdir = bzrdir
2790
def _get_configobj(self):
2791
medium = self._bzrdir._client._medium
2792
verb = 'BzrDir.get_config_file'
2793
if medium._is_remote_before((1, 15)):
2794
raise errors.UnknownSmartMethod(verb)
2795
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2796
response = self._bzrdir._call_expecting_body(
2798
return self._response_to_configobj(response)
2800
def _vfs_get_option(self, name, section, default):
2801
return self._real_object()._get_config().get_option(
2802
name, section, default)
2804
def set_option(self, value, name, section=None):
2805
"""Set the value associated with a named option.
2807
:param value: The value to set
2808
:param name: The name of the value to set
2809
:param section: The section the option is in (if any)
2811
return self._real_object()._get_config().set_option(
2812
value, name, section)
2814
def _real_object(self):
2815
self._bzrdir._ensure_real()
2816
return self._bzrdir._real_bzrdir
2820
def _extract_tar(tar, to_dir):
2821
"""Extract all the contents of a tarfile object.
2823
A replacement for extractall, which is not present in python2.4
2826
tar.extract(tarinfo, to_dir)
2829
def _translate_error(err, **context):
2830
"""Translate an ErrorFromSmartServer into a more useful error.
2832
Possible context keys:
2840
If the error from the server doesn't match a known pattern, then
2841
UnknownErrorFromSmartServer is raised.
2845
return context[name]
2846
except KeyError, key_err:
2847
mutter('Missing key %r in context %r', key_err.args[0], context)
2850
"""Get the path from the context if present, otherwise use first error
2854
return context['path']
2855
except KeyError, key_err:
2857
return err.error_args[0]
2858
except IndexError, idx_err:
2860
'Missing key %r in context %r', key_err.args[0], context)
2863
if err.error_verb == 'IncompatibleRepositories':
2864
raise errors.IncompatibleRepositories(err.error_args[0],
2865
err.error_args[1], err.error_args[2])
2866
elif err.error_verb == 'NoSuchRevision':
2867
raise NoSuchRevision(find('branch'), err.error_args[0])
2868
elif err.error_verb == 'nosuchrevision':
2869
raise NoSuchRevision(find('repository'), err.error_args[0])
2870
elif err.error_verb == 'nobranch':
2871
if len(err.error_args) >= 1:
2872
extra = err.error_args[0]
2875
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2877
elif err.error_verb == 'norepository':
2878
raise errors.NoRepositoryPresent(find('bzrdir'))
2879
elif err.error_verb == 'LockContention':
2880
raise errors.LockContention('(remote lock)')
2881
elif err.error_verb == 'UnlockableTransport':
2882
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2883
elif err.error_verb == 'LockFailed':
2884
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2885
elif err.error_verb == 'TokenMismatch':
2886
raise errors.TokenMismatch(find('token'), '(remote token)')
2887
elif err.error_verb == 'Diverged':
2888
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2889
elif err.error_verb == 'TipChangeRejected':
2890
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2891
elif err.error_verb == 'UnstackableBranchFormat':
2892
raise errors.UnstackableBranchFormat(*err.error_args)
2893
elif err.error_verb == 'UnstackableRepositoryFormat':
2894
raise errors.UnstackableRepositoryFormat(*err.error_args)
2895
elif err.error_verb == 'NotStacked':
2896
raise errors.NotStacked(branch=find('branch'))
2897
elif err.error_verb == 'PermissionDenied':
2899
if len(err.error_args) >= 2:
2900
extra = err.error_args[1]
2903
raise errors.PermissionDenied(path, extra=extra)
2904
elif err.error_verb == 'ReadError':
2906
raise errors.ReadError(path)
2907
elif err.error_verb == 'NoSuchFile':
2909
raise errors.NoSuchFile(path)
2910
elif err.error_verb == 'FileExists':
2911
raise errors.FileExists(err.error_args[0])
2912
elif err.error_verb == 'DirectoryNotEmpty':
2913
raise errors.DirectoryNotEmpty(err.error_args[0])
2914
elif err.error_verb == 'ShortReadvError':
2915
args = err.error_args
2916
raise errors.ShortReadvError(
2917
args[0], int(args[1]), int(args[2]), int(args[3]))
2918
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2919
encoding = str(err.error_args[0]) # encoding must always be a string
2920
val = err.error_args[1]
2921
start = int(err.error_args[2])
2922
end = int(err.error_args[3])
2923
reason = str(err.error_args[4]) # reason must always be a string
2924
if val.startswith('u:'):
2925
val = val[2:].decode('utf-8')
2926
elif val.startswith('s:'):
2927
val = val[2:].decode('base64')
2928
if err.error_verb == 'UnicodeDecodeError':
2929
raise UnicodeDecodeError(encoding, val, start, end, reason)
2930
elif err.error_verb == 'UnicodeEncodeError':
2931
raise UnicodeEncodeError(encoding, val, start, end, reason)
2932
elif err.error_verb == 'ReadOnlyError':
2933
raise errors.TransportNotPossible('readonly transport')
2934
raise errors.UnknownErrorFromSmartServer(err)