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.user_url for repo in
1248
self._real_repository._fallback_repositories]
1249
if repository.user_url 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
# TODO: deprecate self.base in favor of user_url
2185
self.base = self.bzrdir.user_url
2187
self._control_files = None
2188
self._lock_mode = None
2189
self._lock_token = None
2190
self._repo_lock_token = None
2191
self._lock_count = 0
2192
self._leave_lock = False
2193
# Setup a format: note that we cannot call _ensure_real until all the
2194
# attributes above are set: This code cannot be moved higher up in this
2197
self._format = RemoteBranchFormat()
2198
if real_branch is not None:
2199
self._format._network_name = \
2200
self._real_branch._format.network_name()
2202
self._format = format
2203
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2204
# branch.open_branch method.
2205
self._real_ignore_fallbacks = not setup_stacking
2206
if not self._format._network_name:
2207
# Did not get from open_branchV2 - old server.
2209
self._format._network_name = \
2210
self._real_branch._format.network_name()
2211
self.tags = self._format.make_tags(self)
2212
# The base class init is not called, so we duplicate this:
2213
hooks = branch.Branch.hooks['open']
2216
self._is_stacked = False
2218
self._setup_stacking()
2220
def _setup_stacking(self):
2221
# configure stacking into the remote repository, by reading it from
2224
fallback_url = self.get_stacked_on_url()
2225
except (errors.NotStacked, errors.UnstackableBranchFormat,
2226
errors.UnstackableRepositoryFormat), e:
2228
self._is_stacked = True
2229
self._activate_fallback_location(fallback_url)
2231
def _get_config(self):
2232
return RemoteBranchConfig(self)
2234
def _get_real_transport(self):
2235
# if we try vfs access, return the real branch's vfs transport
2237
return self._real_branch._transport
2239
_transport = property(_get_real_transport)
2242
return "%s(%s)" % (self.__class__.__name__, self.base)
2246
def _ensure_real(self):
2247
"""Ensure that there is a _real_branch set.
2249
Used before calls to self._real_branch.
2251
if self._real_branch is None:
2252
if not vfs.vfs_enabled():
2253
raise AssertionError('smart server vfs must be enabled '
2254
'to use vfs implementation')
2255
self.bzrdir._ensure_real()
2256
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2257
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2258
if self.repository._real_repository is None:
2259
# Give the remote repository the matching real repo.
2260
real_repo = self._real_branch.repository
2261
if isinstance(real_repo, RemoteRepository):
2262
real_repo._ensure_real()
2263
real_repo = real_repo._real_repository
2264
self.repository._set_real_repository(real_repo)
2265
# Give the real branch the remote repository to let fast-pathing
2267
self._real_branch.repository = self.repository
2268
if self._lock_mode == 'r':
2269
self._real_branch.lock_read()
2270
elif self._lock_mode == 'w':
2271
self._real_branch.lock_write(token=self._lock_token)
2273
def _translate_error(self, err, **context):
2274
self.repository._translate_error(err, branch=self, **context)
2276
def _clear_cached_state(self):
2277
super(RemoteBranch, self)._clear_cached_state()
2278
if self._real_branch is not None:
2279
self._real_branch._clear_cached_state()
2281
def _clear_cached_state_of_remote_branch_only(self):
2282
"""Like _clear_cached_state, but doesn't clear the cache of
2285
This is useful when falling back to calling a method of
2286
self._real_branch that changes state. In that case the underlying
2287
branch changes, so we need to invalidate this RemoteBranch's cache of
2288
it. However, there's no need to invalidate the _real_branch's cache
2289
too, in fact doing so might harm performance.
2291
super(RemoteBranch, self)._clear_cached_state()
2294
def control_files(self):
2295
# Defer actually creating RemoteBranchLockableFiles until its needed,
2296
# because it triggers an _ensure_real that we otherwise might not need.
2297
if self._control_files is None:
2298
self._control_files = RemoteBranchLockableFiles(
2299
self.bzrdir, self._client)
2300
return self._control_files
2302
def _get_checkout_format(self):
2304
return self._real_branch._get_checkout_format()
2306
def get_physical_lock_status(self):
2307
"""See Branch.get_physical_lock_status()."""
2308
# should be an API call to the server, as branches must be lockable.
2310
return self._real_branch.get_physical_lock_status()
2312
def get_stacked_on_url(self):
2313
"""Get the URL this branch is stacked against.
2315
:raises NotStacked: If the branch is not stacked.
2316
:raises UnstackableBranchFormat: If the branch does not support
2318
:raises UnstackableRepositoryFormat: If the repository does not support
2322
# there may not be a repository yet, so we can't use
2323
# self._translate_error, so we can't use self._call either.
2324
response = self._client.call('Branch.get_stacked_on_url',
2325
self._remote_path())
2326
except errors.ErrorFromSmartServer, err:
2327
# there may not be a repository yet, so we can't call through
2328
# its _translate_error
2329
_translate_error(err, branch=self)
2330
except errors.UnknownSmartMethod, err:
2332
return self._real_branch.get_stacked_on_url()
2333
if response[0] != 'ok':
2334
raise errors.UnexpectedSmartServerResponse(response)
2337
def set_stacked_on_url(self, url):
2338
branch.Branch.set_stacked_on_url(self, url)
2340
self._is_stacked = False
2342
self._is_stacked = True
2344
def _vfs_get_tags_bytes(self):
2346
return self._real_branch._get_tags_bytes()
2348
def _get_tags_bytes(self):
2349
medium = self._client._medium
2350
if medium._is_remote_before((1, 13)):
2351
return self._vfs_get_tags_bytes()
2353
response = self._call('Branch.get_tags_bytes', self._remote_path())
2354
except errors.UnknownSmartMethod:
2355
medium._remember_remote_is_before((1, 13))
2356
return self._vfs_get_tags_bytes()
2359
def _vfs_set_tags_bytes(self, bytes):
2361
return self._real_branch._set_tags_bytes(bytes)
2363
def _set_tags_bytes(self, bytes):
2364
medium = self._client._medium
2365
if medium._is_remote_before((1, 18)):
2366
self._vfs_set_tags_bytes(bytes)
2370
self._remote_path(), self._lock_token, self._repo_lock_token)
2371
response = self._call_with_body_bytes(
2372
'Branch.set_tags_bytes', args, bytes)
2373
except errors.UnknownSmartMethod:
2374
medium._remember_remote_is_before((1, 18))
2375
self._vfs_set_tags_bytes(bytes)
2377
def lock_read(self):
2378
self.repository.lock_read()
2379
if not self._lock_mode:
2380
self._note_lock('r')
2381
self._lock_mode = 'r'
2382
self._lock_count = 1
2383
if self._real_branch is not None:
2384
self._real_branch.lock_read()
2386
self._lock_count += 1
2388
def _remote_lock_write(self, token):
2390
branch_token = repo_token = ''
2392
branch_token = token
2393
repo_token = self.repository.lock_write()
2394
self.repository.unlock()
2395
err_context = {'token': token}
2396
response = self._call(
2397
'Branch.lock_write', self._remote_path(), branch_token,
2398
repo_token or '', **err_context)
2399
if response[0] != 'ok':
2400
raise errors.UnexpectedSmartServerResponse(response)
2401
ok, branch_token, repo_token = response
2402
return branch_token, repo_token
2404
def lock_write(self, token=None):
2405
if not self._lock_mode:
2406
self._note_lock('w')
2407
# Lock the branch and repo in one remote call.
2408
remote_tokens = self._remote_lock_write(token)
2409
self._lock_token, self._repo_lock_token = remote_tokens
2410
if not self._lock_token:
2411
raise SmartProtocolError('Remote server did not return a token!')
2412
# Tell the self.repository object that it is locked.
2413
self.repository.lock_write(
2414
self._repo_lock_token, _skip_rpc=True)
2416
if self._real_branch is not None:
2417
self._real_branch.lock_write(token=self._lock_token)
2418
if token is not None:
2419
self._leave_lock = True
2421
self._leave_lock = False
2422
self._lock_mode = 'w'
2423
self._lock_count = 1
2424
elif self._lock_mode == 'r':
2425
raise errors.ReadOnlyTransaction
2427
if token is not None:
2428
# A token was given to lock_write, and we're relocking, so
2429
# check that the given token actually matches the one we
2431
if token != self._lock_token:
2432
raise errors.TokenMismatch(token, self._lock_token)
2433
self._lock_count += 1
2434
# Re-lock the repository too.
2435
self.repository.lock_write(self._repo_lock_token)
2436
return self._lock_token or None
2438
def _unlock(self, branch_token, repo_token):
2439
err_context = {'token': str((branch_token, repo_token))}
2440
response = self._call(
2441
'Branch.unlock', self._remote_path(), branch_token,
2442
repo_token or '', **err_context)
2443
if response == ('ok',):
2445
raise errors.UnexpectedSmartServerResponse(response)
2447
@only_raises(errors.LockNotHeld, errors.LockBroken)
2450
self._lock_count -= 1
2451
if not self._lock_count:
2452
self._clear_cached_state()
2453
mode = self._lock_mode
2454
self._lock_mode = None
2455
if self._real_branch is not None:
2456
if (not self._leave_lock and mode == 'w' and
2457
self._repo_lock_token):
2458
# If this RemoteBranch will remove the physical lock
2459
# for the repository, make sure the _real_branch
2460
# doesn't do it first. (Because the _real_branch's
2461
# repository is set to be the RemoteRepository.)
2462
self._real_branch.repository.leave_lock_in_place()
2463
self._real_branch.unlock()
2465
# Only write-locked branched need to make a remote method
2466
# call to perform the unlock.
2468
if not self._lock_token:
2469
raise AssertionError('Locked, but no token!')
2470
branch_token = self._lock_token
2471
repo_token = self._repo_lock_token
2472
self._lock_token = None
2473
self._repo_lock_token = None
2474
if not self._leave_lock:
2475
self._unlock(branch_token, repo_token)
2477
self.repository.unlock()
2479
def break_lock(self):
2481
return self._real_branch.break_lock()
2483
def leave_lock_in_place(self):
2484
if not self._lock_token:
2485
raise NotImplementedError(self.leave_lock_in_place)
2486
self._leave_lock = True
2488
def dont_leave_lock_in_place(self):
2489
if not self._lock_token:
2490
raise NotImplementedError(self.dont_leave_lock_in_place)
2491
self._leave_lock = False
2494
def get_rev_id(self, revno, history=None):
2496
return _mod_revision.NULL_REVISION
2497
last_revision_info = self.last_revision_info()
2498
ok, result = self.repository.get_rev_id_for_revno(
2499
revno, last_revision_info)
2502
missing_parent = result[1]
2503
# Either the revision named by the server is missing, or its parent
2504
# is. Call get_parent_map to determine which, so that we report a
2506
parent_map = self.repository.get_parent_map([missing_parent])
2507
if missing_parent in parent_map:
2508
missing_parent = parent_map[missing_parent]
2509
raise errors.RevisionNotPresent(missing_parent, self.repository)
2511
def _last_revision_info(self):
2512
response = self._call('Branch.last_revision_info', self._remote_path())
2513
if response[0] != 'ok':
2514
raise SmartProtocolError('unexpected response code %s' % (response,))
2515
revno = int(response[1])
2516
last_revision = response[2]
2517
return (revno, last_revision)
2519
def _gen_revision_history(self):
2520
"""See Branch._gen_revision_history()."""
2521
if self._is_stacked:
2523
return self._real_branch._gen_revision_history()
2524
response_tuple, response_handler = self._call_expecting_body(
2525
'Branch.revision_history', self._remote_path())
2526
if response_tuple[0] != 'ok':
2527
raise errors.UnexpectedSmartServerResponse(response_tuple)
2528
result = response_handler.read_body_bytes().split('\x00')
2533
def _remote_path(self):
2534
return self.bzrdir._path_for_remote_call(self._client)
2536
def _set_last_revision_descendant(self, revision_id, other_branch,
2537
allow_diverged=False, allow_overwrite_descendant=False):
2538
# This performs additional work to meet the hook contract; while its
2539
# undesirable, we have to synthesise the revno to call the hook, and
2540
# not calling the hook is worse as it means changes can't be prevented.
2541
# Having calculated this though, we can't just call into
2542
# set_last_revision_info as a simple call, because there is a set_rh
2543
# hook that some folk may still be using.
2544
old_revno, old_revid = self.last_revision_info()
2545
history = self._lefthand_history(revision_id)
2546
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2547
err_context = {'other_branch': other_branch}
2548
response = self._call('Branch.set_last_revision_ex',
2549
self._remote_path(), self._lock_token, self._repo_lock_token,
2550
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2552
self._clear_cached_state()
2553
if len(response) != 3 and response[0] != 'ok':
2554
raise errors.UnexpectedSmartServerResponse(response)
2555
new_revno, new_revision_id = response[1:]
2556
self._last_revision_info_cache = new_revno, new_revision_id
2557
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2558
if self._real_branch is not None:
2559
cache = new_revno, new_revision_id
2560
self._real_branch._last_revision_info_cache = cache
2562
def _set_last_revision(self, revision_id):
2563
old_revno, old_revid = self.last_revision_info()
2564
# This performs additional work to meet the hook contract; while its
2565
# undesirable, we have to synthesise the revno to call the hook, and
2566
# not calling the hook is worse as it means changes can't be prevented.
2567
# Having calculated this though, we can't just call into
2568
# set_last_revision_info as a simple call, because there is a set_rh
2569
# hook that some folk may still be using.
2570
history = self._lefthand_history(revision_id)
2571
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2572
self._clear_cached_state()
2573
response = self._call('Branch.set_last_revision',
2574
self._remote_path(), self._lock_token, self._repo_lock_token,
2576
if response != ('ok',):
2577
raise errors.UnexpectedSmartServerResponse(response)
2578
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2581
def set_revision_history(self, rev_history):
2582
# Send just the tip revision of the history; the server will generate
2583
# the full history from that. If the revision doesn't exist in this
2584
# branch, NoSuchRevision will be raised.
2585
if rev_history == []:
2588
rev_id = rev_history[-1]
2589
self._set_last_revision(rev_id)
2590
for hook in branch.Branch.hooks['set_rh']:
2591
hook(self, rev_history)
2592
self._cache_revision_history(rev_history)
2594
def _get_parent_location(self):
2595
medium = self._client._medium
2596
if medium._is_remote_before((1, 13)):
2597
return self._vfs_get_parent_location()
2599
response = self._call('Branch.get_parent', self._remote_path())
2600
except errors.UnknownSmartMethod:
2601
medium._remember_remote_is_before((1, 13))
2602
return self._vfs_get_parent_location()
2603
if len(response) != 1:
2604
raise errors.UnexpectedSmartServerResponse(response)
2605
parent_location = response[0]
2606
if parent_location == '':
2608
return parent_location
2610
def _vfs_get_parent_location(self):
2612
return self._real_branch._get_parent_location()
2614
def _set_parent_location(self, url):
2615
medium = self._client._medium
2616
if medium._is_remote_before((1, 15)):
2617
return self._vfs_set_parent_location(url)
2619
call_url = url or ''
2620
if type(call_url) is not str:
2621
raise AssertionError('url must be a str or None (%s)' % url)
2622
response = self._call('Branch.set_parent_location',
2623
self._remote_path(), self._lock_token, self._repo_lock_token,
2625
except errors.UnknownSmartMethod:
2626
medium._remember_remote_is_before((1, 15))
2627
return self._vfs_set_parent_location(url)
2629
raise errors.UnexpectedSmartServerResponse(response)
2631
def _vfs_set_parent_location(self, url):
2633
return self._real_branch._set_parent_location(url)
2636
def pull(self, source, overwrite=False, stop_revision=None,
2638
self._clear_cached_state_of_remote_branch_only()
2640
return self._real_branch.pull(
2641
source, overwrite=overwrite, stop_revision=stop_revision,
2642
_override_hook_target=self, **kwargs)
2645
def push(self, target, overwrite=False, stop_revision=None):
2647
return self._real_branch.push(
2648
target, overwrite=overwrite, stop_revision=stop_revision,
2649
_override_hook_source_branch=self)
2651
def is_locked(self):
2652
return self._lock_count >= 1
2655
def revision_id_to_revno(self, revision_id):
2657
return self._real_branch.revision_id_to_revno(revision_id)
2660
def set_last_revision_info(self, revno, revision_id):
2661
# XXX: These should be returned by the set_last_revision_info verb
2662
old_revno, old_revid = self.last_revision_info()
2663
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2664
revision_id = ensure_null(revision_id)
2666
response = self._call('Branch.set_last_revision_info',
2667
self._remote_path(), self._lock_token, self._repo_lock_token,
2668
str(revno), revision_id)
2669
except errors.UnknownSmartMethod:
2671
self._clear_cached_state_of_remote_branch_only()
2672
self._real_branch.set_last_revision_info(revno, revision_id)
2673
self._last_revision_info_cache = revno, revision_id
2675
if response == ('ok',):
2676
self._clear_cached_state()
2677
self._last_revision_info_cache = revno, revision_id
2678
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2679
# Update the _real_branch's cache too.
2680
if self._real_branch is not None:
2681
cache = self._last_revision_info_cache
2682
self._real_branch._last_revision_info_cache = cache
2684
raise errors.UnexpectedSmartServerResponse(response)
2687
def generate_revision_history(self, revision_id, last_rev=None,
2689
medium = self._client._medium
2690
if not medium._is_remote_before((1, 6)):
2691
# Use a smart method for 1.6 and above servers
2693
self._set_last_revision_descendant(revision_id, other_branch,
2694
allow_diverged=True, allow_overwrite_descendant=True)
2696
except errors.UnknownSmartMethod:
2697
medium._remember_remote_is_before((1, 6))
2698
self._clear_cached_state_of_remote_branch_only()
2699
self.set_revision_history(self._lefthand_history(revision_id,
2700
last_rev=last_rev,other_branch=other_branch))
2702
def set_push_location(self, location):
2704
return self._real_branch.set_push_location(location)
2707
class RemoteConfig(object):
2708
"""A Config that reads and writes from smart verbs.
2710
It is a low-level object that considers config data to be name/value pairs
2711
that may be associated with a section. Assigning meaning to the these
2712
values is done at higher levels like bzrlib.config.TreeConfig.
2715
def get_option(self, name, section=None, default=None):
2716
"""Return the value associated with a named option.
2718
:param name: The name of the value
2719
:param section: The section the option is in (if any)
2720
:param default: The value to return if the value is not set
2721
:return: The value or default value
2724
configobj = self._get_configobj()
2726
section_obj = configobj
2729
section_obj = configobj[section]
2732
return section_obj.get(name, default)
2733
except errors.UnknownSmartMethod:
2734
return self._vfs_get_option(name, section, default)
2736
def _response_to_configobj(self, response):
2737
if len(response[0]) and response[0][0] != 'ok':
2738
raise errors.UnexpectedSmartServerResponse(response)
2739
lines = response[1].read_body_bytes().splitlines()
2740
return config.ConfigObj(lines, encoding='utf-8')
2743
class RemoteBranchConfig(RemoteConfig):
2744
"""A RemoteConfig for Branches."""
2746
def __init__(self, branch):
2747
self._branch = branch
2749
def _get_configobj(self):
2750
path = self._branch._remote_path()
2751
response = self._branch._client.call_expecting_body(
2752
'Branch.get_config_file', path)
2753
return self._response_to_configobj(response)
2755
def set_option(self, value, name, section=None):
2756
"""Set the value associated with a named option.
2758
:param value: The value to set
2759
:param name: The name of the value to set
2760
:param section: The section the option is in (if any)
2762
medium = self._branch._client._medium
2763
if medium._is_remote_before((1, 14)):
2764
return self._vfs_set_option(value, name, section)
2766
path = self._branch._remote_path()
2767
response = self._branch._client.call('Branch.set_config_option',
2768
path, self._branch._lock_token, self._branch._repo_lock_token,
2769
value.encode('utf8'), name, section or '')
2770
except errors.UnknownSmartMethod:
2771
medium._remember_remote_is_before((1, 14))
2772
return self._vfs_set_option(value, name, section)
2774
raise errors.UnexpectedSmartServerResponse(response)
2776
def _real_object(self):
2777
self._branch._ensure_real()
2778
return self._branch._real_branch
2780
def _vfs_set_option(self, value, name, section=None):
2781
return self._real_object()._get_config().set_option(
2782
value, name, section)
2785
class RemoteBzrDirConfig(RemoteConfig):
2786
"""A RemoteConfig for BzrDirs."""
2788
def __init__(self, bzrdir):
2789
self._bzrdir = bzrdir
2791
def _get_configobj(self):
2792
medium = self._bzrdir._client._medium
2793
verb = 'BzrDir.get_config_file'
2794
if medium._is_remote_before((1, 15)):
2795
raise errors.UnknownSmartMethod(verb)
2796
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2797
response = self._bzrdir._call_expecting_body(
2799
return self._response_to_configobj(response)
2801
def _vfs_get_option(self, name, section, default):
2802
return self._real_object()._get_config().get_option(
2803
name, section, default)
2805
def set_option(self, value, name, section=None):
2806
"""Set the value associated with a named option.
2808
:param value: The value to set
2809
:param name: The name of the value to set
2810
:param section: The section the option is in (if any)
2812
return self._real_object()._get_config().set_option(
2813
value, name, section)
2815
def _real_object(self):
2816
self._bzrdir._ensure_real()
2817
return self._bzrdir._real_bzrdir
2821
def _extract_tar(tar, to_dir):
2822
"""Extract all the contents of a tarfile object.
2824
A replacement for extractall, which is not present in python2.4
2827
tar.extract(tarinfo, to_dir)
2830
def _translate_error(err, **context):
2831
"""Translate an ErrorFromSmartServer into a more useful error.
2833
Possible context keys:
2841
If the error from the server doesn't match a known pattern, then
2842
UnknownErrorFromSmartServer is raised.
2846
return context[name]
2847
except KeyError, key_err:
2848
mutter('Missing key %r in context %r', key_err.args[0], context)
2851
"""Get the path from the context if present, otherwise use first error
2855
return context['path']
2856
except KeyError, key_err:
2858
return err.error_args[0]
2859
except IndexError, idx_err:
2861
'Missing key %r in context %r', key_err.args[0], context)
2864
if err.error_verb == 'IncompatibleRepositories':
2865
raise errors.IncompatibleRepositories(err.error_args[0],
2866
err.error_args[1], err.error_args[2])
2867
elif err.error_verb == 'NoSuchRevision':
2868
raise NoSuchRevision(find('branch'), err.error_args[0])
2869
elif err.error_verb == 'nosuchrevision':
2870
raise NoSuchRevision(find('repository'), err.error_args[0])
2871
elif err.error_verb == 'nobranch':
2872
if len(err.error_args) >= 1:
2873
extra = err.error_args[0]
2876
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2878
elif err.error_verb == 'norepository':
2879
raise errors.NoRepositoryPresent(find('bzrdir'))
2880
elif err.error_verb == 'LockContention':
2881
raise errors.LockContention('(remote lock)')
2882
elif err.error_verb == 'UnlockableTransport':
2883
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2884
elif err.error_verb == 'LockFailed':
2885
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2886
elif err.error_verb == 'TokenMismatch':
2887
raise errors.TokenMismatch(find('token'), '(remote token)')
2888
elif err.error_verb == 'Diverged':
2889
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2890
elif err.error_verb == 'TipChangeRejected':
2891
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2892
elif err.error_verb == 'UnstackableBranchFormat':
2893
raise errors.UnstackableBranchFormat(*err.error_args)
2894
elif err.error_verb == 'UnstackableRepositoryFormat':
2895
raise errors.UnstackableRepositoryFormat(*err.error_args)
2896
elif err.error_verb == 'NotStacked':
2897
raise errors.NotStacked(branch=find('branch'))
2898
elif err.error_verb == 'PermissionDenied':
2900
if len(err.error_args) >= 2:
2901
extra = err.error_args[1]
2904
raise errors.PermissionDenied(path, extra=extra)
2905
elif err.error_verb == 'ReadError':
2907
raise errors.ReadError(path)
2908
elif err.error_verb == 'NoSuchFile':
2910
raise errors.NoSuchFile(path)
2911
elif err.error_verb == 'FileExists':
2912
raise errors.FileExists(err.error_args[0])
2913
elif err.error_verb == 'DirectoryNotEmpty':
2914
raise errors.DirectoryNotEmpty(err.error_args[0])
2915
elif err.error_verb == 'ShortReadvError':
2916
args = err.error_args
2917
raise errors.ShortReadvError(
2918
args[0], int(args[1]), int(args[2]), int(args[3]))
2919
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2920
encoding = str(err.error_args[0]) # encoding must always be a string
2921
val = err.error_args[1]
2922
start = int(err.error_args[2])
2923
end = int(err.error_args[3])
2924
reason = str(err.error_args[4]) # reason must always be a string
2925
if val.startswith('u:'):
2926
val = val[2:].decode('utf-8')
2927
elif val.startswith('s:'):
2928
val = val[2:].decode('base64')
2929
if err.error_verb == 'UnicodeDecodeError':
2930
raise UnicodeDecodeError(encoding, val, start, end, reason)
2931
elif err.error_verb == 'UnicodeEncodeError':
2932
raise UnicodeEncodeError(encoding, val, start, end, reason)
2933
elif err.error_verb == 'ReadOnlyError':
2934
raise errors.TransportNotPossible('readonly transport')
2935
raise errors.UnknownErrorFromSmartServer(err)