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
30
repository as _mod_repository,
32
revision as _mod_revision,
36
from bzrlib.branch import BranchReferenceFormat
37
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
39
from bzrlib.errors import (
43
from bzrlib.lockable_files import LockableFiles
44
from bzrlib.smart import client, vfs, repository as smart_repo
45
from bzrlib.revision import ensure_null, NULL_REVISION
46
from bzrlib.trace import mutter, note, warning
49
class _RpcHelper(object):
50
"""Mixin class that helps with issuing RPCs."""
52
def _call(self, method, *args, **err_context):
54
return self._client.call(method, *args)
55
except errors.ErrorFromSmartServer, err:
56
self._translate_error(err, **err_context)
58
def _call_expecting_body(self, method, *args, **err_context):
60
return self._client.call_expecting_body(method, *args)
61
except errors.ErrorFromSmartServer, err:
62
self._translate_error(err, **err_context)
64
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
66
return self._client.call_with_body_bytes(method, args, body_bytes)
67
except errors.ErrorFromSmartServer, err:
68
self._translate_error(err, **err_context)
70
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
73
return self._client.call_with_body_bytes_expecting_body(
74
method, args, body_bytes)
75
except errors.ErrorFromSmartServer, err:
76
self._translate_error(err, **err_context)
79
def response_tuple_to_repo_format(response):
80
"""Convert a response tuple describing a repository format to a format."""
81
format = RemoteRepositoryFormat()
82
format._rich_root_data = (response[0] == 'yes')
83
format._supports_tree_reference = (response[1] == 'yes')
84
format._supports_external_lookups = (response[2] == 'yes')
85
format._network_name = response[3]
89
# Note: RemoteBzrDirFormat is in bzrdir.py
91
class RemoteBzrDir(BzrDir, _RpcHelper):
92
"""Control directory on a remote server, accessed via bzr:// or similar."""
94
def __init__(self, transport, format, _client=None, _force_probe=False):
95
"""Construct a RemoteBzrDir.
97
:param _client: Private parameter for testing. Disables probing and the
100
BzrDir.__init__(self, transport, format)
101
# this object holds a delegated bzrdir that uses file-level operations
102
# to talk to the other side
103
self._real_bzrdir = None
104
self._has_working_tree = None
105
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
106
# create_branch for details.
107
self._next_open_branch_result = None
110
medium = transport.get_smart_medium()
111
self._client = client._SmartClient(medium)
113
self._client = _client
120
return '%s(%r)' % (self.__class__.__name__, self._client)
122
def _probe_bzrdir(self):
123
medium = self._client._medium
124
path = self._path_for_remote_call(self._client)
125
if medium._is_remote_before((2, 1)):
129
self._rpc_open_2_1(path)
131
except errors.UnknownSmartMethod:
132
medium._remember_remote_is_before((2, 1))
135
def _rpc_open_2_1(self, path):
136
response = self._call('BzrDir.open_2.1', path)
137
if response == ('no',):
138
raise errors.NotBranchError(path=self.root_transport.base)
139
elif response[0] == 'yes':
140
if response[1] == 'yes':
141
self._has_working_tree = True
142
elif response[1] == 'no':
143
self._has_working_tree = False
145
raise errors.UnexpectedSmartServerResponse(response)
147
raise errors.UnexpectedSmartServerResponse(response)
149
def _rpc_open(self, path):
150
response = self._call('BzrDir.open', path)
151
if response not in [('yes',), ('no',)]:
152
raise errors.UnexpectedSmartServerResponse(response)
153
if response == ('no',):
154
raise errors.NotBranchError(path=self.root_transport.base)
156
def _ensure_real(self):
157
"""Ensure that there is a _real_bzrdir set.
159
Used before calls to self._real_bzrdir.
161
if not self._real_bzrdir:
162
if 'hpssvfs' in debug.debug_flags:
164
warning('VFS BzrDir access triggered\n%s',
165
''.join(traceback.format_stack()))
166
self._real_bzrdir = BzrDir.open_from_transport(
167
self.root_transport, _server_formats=False)
168
self._format._network_name = \
169
self._real_bzrdir._format.network_name()
171
def _translate_error(self, err, **context):
172
_translate_error(err, bzrdir=self, **context)
174
def break_lock(self):
175
# Prevent aliasing problems in the next_open_branch_result cache.
176
# See create_branch for rationale.
177
self._next_open_branch_result = None
178
return BzrDir.break_lock(self)
180
def _vfs_cloning_metadir(self, require_stacking=False):
182
return self._real_bzrdir.cloning_metadir(
183
require_stacking=require_stacking)
185
def cloning_metadir(self, require_stacking=False):
186
medium = self._client._medium
187
if medium._is_remote_before((1, 13)):
188
return self._vfs_cloning_metadir(require_stacking=require_stacking)
189
verb = 'BzrDir.cloning_metadir'
194
path = self._path_for_remote_call(self._client)
196
response = self._call(verb, path, stacking)
197
except errors.UnknownSmartMethod:
198
medium._remember_remote_is_before((1, 13))
199
return self._vfs_cloning_metadir(require_stacking=require_stacking)
200
except errors.UnknownErrorFromSmartServer, err:
201
if err.error_tuple != ('BranchReference',):
203
# We need to resolve the branch reference to determine the
204
# cloning_metadir. This causes unnecessary RPCs to open the
205
# referenced branch (and bzrdir, etc) but only when the caller
206
# didn't already resolve the branch reference.
207
referenced_branch = self.open_branch()
208
return referenced_branch.bzrdir.cloning_metadir()
209
if len(response) != 3:
210
raise errors.UnexpectedSmartServerResponse(response)
211
control_name, repo_name, branch_info = response
212
if len(branch_info) != 2:
213
raise errors.UnexpectedSmartServerResponse(response)
214
branch_ref, branch_name = branch_info
215
format = bzrdir.network_format_registry.get(control_name)
217
format.repository_format = repository.network_format_registry.get(
219
if branch_ref == 'ref':
220
# XXX: we need possible_transports here to avoid reopening the
221
# connection to the referenced location
222
ref_bzrdir = BzrDir.open(branch_name)
223
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
224
format.set_branch_format(branch_format)
225
elif branch_ref == 'branch':
227
format.set_branch_format(
228
branch.network_format_registry.get(branch_name))
230
raise errors.UnexpectedSmartServerResponse(response)
233
def create_repository(self, shared=False):
234
# as per meta1 formats - just delegate to the format object which may
236
result = self._format.repository_format.initialize(self, shared)
237
if not isinstance(result, RemoteRepository):
238
return self.open_repository()
242
def destroy_repository(self):
243
"""See BzrDir.destroy_repository"""
245
self._real_bzrdir.destroy_repository()
247
def create_branch(self, name=None):
248
# as per meta1 formats - just delegate to the format object which may
250
real_branch = self._format.get_branch_format().initialize(self,
252
if not isinstance(real_branch, RemoteBranch):
253
result = RemoteBranch(self, self.find_repository(), real_branch,
257
# BzrDir.clone_on_transport() uses the result of create_branch but does
258
# not return it to its callers; we save approximately 8% of our round
259
# trips by handing the branch we created back to the first caller to
260
# open_branch rather than probing anew. Long term we need a API in
261
# bzrdir that doesn't discard result objects (like result_branch).
263
self._next_open_branch_result = result
266
def destroy_branch(self, name=None):
267
"""See BzrDir.destroy_branch"""
269
self._real_bzrdir.destroy_branch(name=name)
270
self._next_open_branch_result = None
272
def create_workingtree(self, revision_id=None, from_branch=None):
273
raise errors.NotLocalUrl(self.transport.base)
275
def find_branch_format(self):
276
"""Find the branch 'format' for this bzrdir.
278
This might be a synthetic object for e.g. RemoteBranch and SVN.
280
b = self.open_branch()
283
def get_branch_reference(self):
284
"""See BzrDir.get_branch_reference()."""
285
response = self._get_branch_reference()
286
if response[0] == 'ref':
291
def _get_branch_reference(self):
292
path = self._path_for_remote_call(self._client)
293
medium = self._client._medium
295
('BzrDir.open_branchV3', (2, 1)),
296
('BzrDir.open_branchV2', (1, 13)),
297
('BzrDir.open_branch', None),
299
for verb, required_version in candidate_calls:
300
if required_version and medium._is_remote_before(required_version):
303
response = self._call(verb, path)
304
except errors.UnknownSmartMethod:
305
if required_version is None:
307
medium._remember_remote_is_before(required_version)
310
if verb == 'BzrDir.open_branch':
311
if response[0] != 'ok':
312
raise errors.UnexpectedSmartServerResponse(response)
313
if response[1] != '':
314
return ('ref', response[1])
316
return ('branch', '')
317
if response[0] not in ('ref', 'branch'):
318
raise errors.UnexpectedSmartServerResponse(response)
321
def _get_tree_branch(self):
322
"""See BzrDir._get_tree_branch()."""
323
return None, self.open_branch()
325
def open_branch(self, name=None, unsupported=False,
326
ignore_fallbacks=False):
328
raise NotImplementedError('unsupported flag support not implemented yet.')
329
if self._next_open_branch_result is not None:
330
# See create_branch for details.
331
result = self._next_open_branch_result
332
self._next_open_branch_result = None
334
response = self._get_branch_reference()
335
if response[0] == 'ref':
336
# a branch reference, use the existing BranchReference logic.
337
format = BranchReferenceFormat()
338
return format.open(self, name=name, _found=True,
339
location=response[1], ignore_fallbacks=ignore_fallbacks)
340
branch_format_name = response[1]
341
if not branch_format_name:
342
branch_format_name = None
343
format = RemoteBranchFormat(network_name=branch_format_name)
344
return RemoteBranch(self, self.find_repository(), format=format,
345
setup_stacking=not ignore_fallbacks, name=name)
347
def _open_repo_v1(self, path):
348
verb = 'BzrDir.find_repository'
349
response = self._call(verb, path)
350
if response[0] != 'ok':
351
raise errors.UnexpectedSmartServerResponse(response)
352
# servers that only support the v1 method don't support external
355
repo = self._real_bzrdir.open_repository()
356
response = response + ('no', repo._format.network_name())
357
return response, repo
359
def _open_repo_v2(self, path):
360
verb = 'BzrDir.find_repositoryV2'
361
response = self._call(verb, path)
362
if response[0] != 'ok':
363
raise errors.UnexpectedSmartServerResponse(response)
365
repo = self._real_bzrdir.open_repository()
366
response = response + (repo._format.network_name(),)
367
return response, repo
369
def _open_repo_v3(self, path):
370
verb = 'BzrDir.find_repositoryV3'
371
medium = self._client._medium
372
if medium._is_remote_before((1, 13)):
373
raise errors.UnknownSmartMethod(verb)
375
response = self._call(verb, path)
376
except errors.UnknownSmartMethod:
377
medium._remember_remote_is_before((1, 13))
379
if response[0] != 'ok':
380
raise errors.UnexpectedSmartServerResponse(response)
381
return response, None
383
def open_repository(self):
384
path = self._path_for_remote_call(self._client)
386
for probe in [self._open_repo_v3, self._open_repo_v2,
389
response, real_repo = probe(path)
391
except errors.UnknownSmartMethod:
394
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
395
if response[0] != 'ok':
396
raise errors.UnexpectedSmartServerResponse(response)
397
if len(response) != 6:
398
raise SmartProtocolError('incorrect response length %s' % (response,))
399
if response[1] == '':
400
# repo is at this dir.
401
format = response_tuple_to_repo_format(response[2:])
402
# Used to support creating a real format instance when needed.
403
format._creating_bzrdir = self
404
remote_repo = RemoteRepository(self, format)
405
format._creating_repo = remote_repo
406
if real_repo is not None:
407
remote_repo._set_real_repository(real_repo)
410
raise errors.NoRepositoryPresent(self)
412
def has_workingtree(self):
413
if self._has_working_tree is None:
415
self._has_working_tree = self._real_bzrdir.has_workingtree()
416
return self._has_working_tree
418
def open_workingtree(self, recommend_upgrade=True):
419
if self.has_workingtree():
420
raise errors.NotLocalUrl(self.root_transport)
422
raise errors.NoWorkingTree(self.root_transport.base)
424
def _path_for_remote_call(self, client):
425
"""Return the path to be used for this bzrdir in a remote call."""
426
return client.remote_path_from_transport(self.root_transport)
428
def get_branch_transport(self, branch_format, name=None):
430
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
432
def get_repository_transport(self, repository_format):
434
return self._real_bzrdir.get_repository_transport(repository_format)
436
def get_workingtree_transport(self, workingtree_format):
438
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
440
def can_convert_format(self):
441
"""Upgrading of remote bzrdirs is not supported yet."""
444
def needs_format_conversion(self, format=None):
445
"""Upgrading of remote bzrdirs is not supported yet."""
447
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
448
% 'needs_format_conversion(format=None)')
451
def clone(self, url, revision_id=None, force_new_repo=False,
452
preserve_stacking=False):
454
return self._real_bzrdir.clone(url, revision_id=revision_id,
455
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
457
def _get_config(self):
458
return RemoteBzrDirConfig(self)
461
class RemoteRepositoryFormat(repository.RepositoryFormat):
462
"""Format for repositories accessed over a _SmartClient.
464
Instances of this repository are represented by RemoteRepository
467
The RemoteRepositoryFormat is parameterized during construction
468
to reflect the capabilities of the real, remote format. Specifically
469
the attributes rich_root_data and supports_tree_reference are set
470
on a per instance basis, and are not set (and should not be) at
473
:ivar _custom_format: If set, a specific concrete repository format that
474
will be used when initializing a repository with this
475
RemoteRepositoryFormat.
476
:ivar _creating_repo: If set, the repository object that this
477
RemoteRepositoryFormat was created for: it can be called into
478
to obtain data like the network name.
481
_matchingbzrdir = RemoteBzrDirFormat()
484
repository.RepositoryFormat.__init__(self)
485
self._custom_format = None
486
self._network_name = None
487
self._creating_bzrdir = None
488
self._supports_chks = None
489
self._supports_external_lookups = None
490
self._supports_tree_reference = None
491
self._rich_root_data = None
494
return "%s(_network_name=%r)" % (self.__class__.__name__,
498
def fast_deltas(self):
500
return self._custom_format.fast_deltas
503
def rich_root_data(self):
504
if self._rich_root_data is None:
506
self._rich_root_data = self._custom_format.rich_root_data
507
return self._rich_root_data
510
def supports_chks(self):
511
if self._supports_chks is None:
513
self._supports_chks = self._custom_format.supports_chks
514
return self._supports_chks
517
def supports_external_lookups(self):
518
if self._supports_external_lookups is None:
520
self._supports_external_lookups = \
521
self._custom_format.supports_external_lookups
522
return self._supports_external_lookups
525
def supports_tree_reference(self):
526
if self._supports_tree_reference is None:
528
self._supports_tree_reference = \
529
self._custom_format.supports_tree_reference
530
return self._supports_tree_reference
532
def _vfs_initialize(self, a_bzrdir, shared):
533
"""Helper for common code in initialize."""
534
if self._custom_format:
535
# Custom format requested
536
result = self._custom_format.initialize(a_bzrdir, shared=shared)
537
elif self._creating_bzrdir is not None:
538
# Use the format that the repository we were created to back
540
prior_repo = self._creating_bzrdir.open_repository()
541
prior_repo._ensure_real()
542
result = prior_repo._real_repository._format.initialize(
543
a_bzrdir, shared=shared)
545
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
546
# support remote initialization.
547
# We delegate to a real object at this point (as RemoteBzrDir
548
# delegate to the repository format which would lead to infinite
549
# recursion if we just called a_bzrdir.create_repository.
550
a_bzrdir._ensure_real()
551
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
552
if not isinstance(result, RemoteRepository):
553
return self.open(a_bzrdir)
557
def initialize(self, a_bzrdir, shared=False):
558
# Being asked to create on a non RemoteBzrDir:
559
if not isinstance(a_bzrdir, RemoteBzrDir):
560
return self._vfs_initialize(a_bzrdir, shared)
561
medium = a_bzrdir._client._medium
562
if medium._is_remote_before((1, 13)):
563
return self._vfs_initialize(a_bzrdir, shared)
564
# Creating on a remote bzr dir.
565
# 1) get the network name to use.
566
if self._custom_format:
567
network_name = self._custom_format.network_name()
568
elif self._network_name:
569
network_name = self._network_name
571
# Select the current bzrlib default and ask for that.
572
reference_bzrdir_format = bzrdir.format_registry.get('default')()
573
reference_format = reference_bzrdir_format.repository_format
574
network_name = reference_format.network_name()
575
# 2) try direct creation via RPC
576
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
577
verb = 'BzrDir.create_repository'
583
response = a_bzrdir._call(verb, path, network_name, shared_str)
584
except errors.UnknownSmartMethod:
585
# Fallback - use vfs methods
586
medium._remember_remote_is_before((1, 13))
587
return self._vfs_initialize(a_bzrdir, shared)
589
# Turn the response into a RemoteRepository object.
590
format = response_tuple_to_repo_format(response[1:])
591
# Used to support creating a real format instance when needed.
592
format._creating_bzrdir = a_bzrdir
593
remote_repo = RemoteRepository(a_bzrdir, format)
594
format._creating_repo = remote_repo
597
def open(self, a_bzrdir):
598
if not isinstance(a_bzrdir, RemoteBzrDir):
599
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
600
return a_bzrdir.open_repository()
602
def _ensure_real(self):
603
if self._custom_format is None:
604
self._custom_format = repository.network_format_registry.get(
608
def _fetch_order(self):
610
return self._custom_format._fetch_order
613
def _fetch_uses_deltas(self):
615
return self._custom_format._fetch_uses_deltas
618
def _fetch_reconcile(self):
620
return self._custom_format._fetch_reconcile
622
def get_format_description(self):
624
return 'Remote: ' + self._custom_format.get_format_description()
626
def __eq__(self, other):
627
return self.__class__ is other.__class__
629
def network_name(self):
630
if self._network_name:
631
return self._network_name
632
self._creating_repo._ensure_real()
633
return self._creating_repo._real_repository._format.network_name()
636
def pack_compresses(self):
638
return self._custom_format.pack_compresses
641
def _serializer(self):
643
return self._custom_format._serializer
646
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
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
return "%s(%s)" % (self.__class__.__name__, self.base)
700
def abort_write_group(self, suppress_errors=False):
701
"""Complete a write group on the decorated repository.
703
Smart methods perform operations in a single step so this API
704
is not really applicable except as a compatibility thunk
705
for older plugins that don't use e.g. the CommitBuilder
708
:param suppress_errors: see Repository.abort_write_group.
711
return self._real_repository.abort_write_group(
712
suppress_errors=suppress_errors)
716
"""Decorate the real repository for now.
718
In the long term a full blown network facility is needed to avoid
719
creating a real repository object locally.
722
return self._real_repository.chk_bytes
724
def commit_write_group(self):
725
"""Complete a write group on the decorated repository.
727
Smart methods perform operations in a single step so this API
728
is not really applicable except as a compatibility thunk
729
for older plugins that don't use e.g. the CommitBuilder
733
return self._real_repository.commit_write_group()
735
def resume_write_group(self, tokens):
737
return self._real_repository.resume_write_group(tokens)
739
def suspend_write_group(self):
741
return self._real_repository.suspend_write_group()
743
def get_missing_parent_inventories(self, check_for_missing_texts=True):
745
return self._real_repository.get_missing_parent_inventories(
746
check_for_missing_texts=check_for_missing_texts)
748
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
750
return self._real_repository.get_rev_id_for_revno(
753
def get_rev_id_for_revno(self, revno, known_pair):
754
"""See Repository.get_rev_id_for_revno."""
755
path = self.bzrdir._path_for_remote_call(self._client)
757
if self._client._medium._is_remote_before((1, 17)):
758
return self._get_rev_id_for_revno_vfs(revno, known_pair)
759
response = self._call(
760
'Repository.get_rev_id_for_revno', path, revno, known_pair)
761
except errors.UnknownSmartMethod:
762
self._client._medium._remember_remote_is_before((1, 17))
763
return self._get_rev_id_for_revno_vfs(revno, known_pair)
764
if response[0] == 'ok':
765
return True, response[1]
766
elif response[0] == 'history-incomplete':
767
known_pair = response[1:3]
768
for fallback in self._fallback_repositories:
769
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
774
# Not found in any fallbacks
775
return False, known_pair
777
raise errors.UnexpectedSmartServerResponse(response)
779
def _ensure_real(self):
780
"""Ensure that there is a _real_repository set.
782
Used before calls to self._real_repository.
784
Note that _ensure_real causes many roundtrips to the server which are
785
not desirable, and prevents the use of smart one-roundtrip RPC's to
786
perform complex operations (such as accessing parent data, streaming
787
revisions etc). Adding calls to _ensure_real should only be done when
788
bringing up new functionality, adding fallbacks for smart methods that
789
require a fallback path, and never to replace an existing smart method
790
invocation. If in doubt chat to the bzr network team.
792
if self._real_repository is None:
793
if 'hpssvfs' in debug.debug_flags:
795
warning('VFS Repository access triggered\n%s',
796
''.join(traceback.format_stack()))
797
self._unstacked_provider.missing_keys.clear()
798
self.bzrdir._ensure_real()
799
self._set_real_repository(
800
self.bzrdir._real_bzrdir.open_repository())
802
def _translate_error(self, err, **context):
803
self.bzrdir._translate_error(err, repository=self, **context)
805
def find_text_key_references(self):
806
"""Find the text key references within the repository.
808
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
809
revision_ids. Each altered file-ids has the exact revision_ids that
810
altered it listed explicitly.
811
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
812
to whether they were referred to by the inventory of the
813
revision_id that they contain. The inventory texts from all present
814
revision ids are assessed to generate this report.
817
return self._real_repository.find_text_key_references()
819
def _generate_text_key_index(self):
820
"""Generate a new text key index for the repository.
822
This is an expensive function that will take considerable time to run.
824
:return: A dict mapping (file_id, revision_id) tuples to a list of
825
parents, also (file_id, revision_id) tuples.
828
return self._real_repository._generate_text_key_index()
830
def _get_revision_graph(self, revision_id):
831
"""Private method for using with old (< 1.2) servers to fallback."""
832
if revision_id is None:
834
elif revision.is_null(revision_id):
837
path = self.bzrdir._path_for_remote_call(self._client)
838
response = self._call_expecting_body(
839
'Repository.get_revision_graph', path, revision_id)
840
response_tuple, response_handler = response
841
if response_tuple[0] != 'ok':
842
raise errors.UnexpectedSmartServerResponse(response_tuple)
843
coded = response_handler.read_body_bytes()
845
# no revisions in this repository!
847
lines = coded.split('\n')
850
d = tuple(line.split())
851
revision_graph[d[0]] = d[1:]
853
return revision_graph
856
"""See Repository._get_sink()."""
857
return RemoteStreamSink(self)
859
def _get_source(self, to_format):
860
"""Return a source for streaming from this repository."""
861
return RemoteStreamSource(self, to_format)
864
def has_revision(self, revision_id):
865
"""True if this repository has a copy of the revision."""
866
# Copy of bzrlib.repository.Repository.has_revision
867
return revision_id in self.has_revisions((revision_id,))
870
def has_revisions(self, revision_ids):
871
"""Probe to find out the presence of multiple revisions.
873
:param revision_ids: An iterable of revision_ids.
874
:return: A set of the revision_ids that were present.
876
# Copy of bzrlib.repository.Repository.has_revisions
877
parent_map = self.get_parent_map(revision_ids)
878
result = set(parent_map)
879
if _mod_revision.NULL_REVISION in revision_ids:
880
result.add(_mod_revision.NULL_REVISION)
883
def _has_same_fallbacks(self, other_repo):
884
"""Returns true if the repositories have the same fallbacks."""
885
# XXX: copied from Repository; it should be unified into a base class
886
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
887
my_fb = self._fallback_repositories
888
other_fb = other_repo._fallback_repositories
889
if len(my_fb) != len(other_fb):
891
for f, g in zip(my_fb, other_fb):
892
if not f.has_same_location(g):
896
def has_same_location(self, other):
897
# TODO: Move to RepositoryBase and unify with the regular Repository
898
# one; unfortunately the tests rely on slightly different behaviour at
899
# present -- mbp 20090710
900
return (self.__class__ is other.__class__ and
901
self.bzrdir.transport.base == other.bzrdir.transport.base)
903
def get_graph(self, other_repository=None):
904
"""Return the graph for this repository format"""
905
parents_provider = self._make_parents_provider(other_repository)
906
return graph.Graph(parents_provider)
909
def get_known_graph_ancestry(self, revision_ids):
910
"""Return the known graph for a set of revision ids and their ancestors.
912
st = static_tuple.StaticTuple
913
revision_keys = [st(r_id).intern() for r_id in revision_ids]
914
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
915
return graph.GraphThunkIdsToKeys(known_graph)
917
def gather_stats(self, revid=None, committers=None):
918
"""See Repository.gather_stats()."""
919
path = self.bzrdir._path_for_remote_call(self._client)
920
# revid can be None to indicate no revisions, not just NULL_REVISION
921
if revid is None or revision.is_null(revid):
925
if committers is None or not committers:
926
fmt_committers = 'no'
928
fmt_committers = 'yes'
929
response_tuple, response_handler = self._call_expecting_body(
930
'Repository.gather_stats', path, fmt_revid, fmt_committers)
931
if response_tuple[0] != 'ok':
932
raise errors.UnexpectedSmartServerResponse(response_tuple)
934
body = response_handler.read_body_bytes()
936
for line in body.split('\n'):
939
key, val_text = line.split(':')
940
if key in ('revisions', 'size', 'committers'):
941
result[key] = int(val_text)
942
elif key in ('firstrev', 'latestrev'):
943
values = val_text.split(' ')[1:]
944
result[key] = (float(values[0]), long(values[1]))
948
def find_branches(self, using=False):
949
"""See Repository.find_branches()."""
950
# should be an API call to the server.
952
return self._real_repository.find_branches(using=using)
954
def get_physical_lock_status(self):
955
"""See Repository.get_physical_lock_status()."""
956
# should be an API call to the server.
958
return self._real_repository.get_physical_lock_status()
960
def is_in_write_group(self):
961
"""Return True if there is an open write group.
963
write groups are only applicable locally for the smart server..
965
if self._real_repository:
966
return self._real_repository.is_in_write_group()
969
return self._lock_count >= 1
972
"""See Repository.is_shared()."""
973
path = self.bzrdir._path_for_remote_call(self._client)
974
response = self._call('Repository.is_shared', path)
975
if response[0] not in ('yes', 'no'):
976
raise SmartProtocolError('unexpected response code %s' % (response,))
977
return response[0] == 'yes'
979
def is_write_locked(self):
980
return self._lock_mode == 'w'
982
def _warn_if_deprecated(self, branch=None):
983
# If we have a real repository, the check will be done there, if we
984
# don't the check will be done remotely.
988
# wrong eventually - want a local lock cache context
989
if not self._lock_mode:
991
self._lock_mode = 'r'
993
self._unstacked_provider.enable_cache(cache_misses=True)
994
if self._real_repository is not None:
995
self._real_repository.lock_read()
996
for repo in self._fallback_repositories:
999
self._lock_count += 1
1001
def _remote_lock_write(self, token):
1002
path = self.bzrdir._path_for_remote_call(self._client)
1005
err_context = {'token': token}
1006
response = self._call('Repository.lock_write', path, token,
1008
if response[0] == 'ok':
1009
ok, token = response
1012
raise errors.UnexpectedSmartServerResponse(response)
1014
def lock_write(self, token=None, _skip_rpc=False):
1015
if not self._lock_mode:
1016
self._note_lock('w')
1018
if self._lock_token is not None:
1019
if token != self._lock_token:
1020
raise errors.TokenMismatch(token, self._lock_token)
1021
self._lock_token = token
1023
self._lock_token = self._remote_lock_write(token)
1024
# if self._lock_token is None, then this is something like packs or
1025
# svn where we don't get to lock the repo, or a weave style repository
1026
# where we cannot lock it over the wire and attempts to do so will
1028
if self._real_repository is not None:
1029
self._real_repository.lock_write(token=self._lock_token)
1030
if token is not None:
1031
self._leave_lock = True
1033
self._leave_lock = False
1034
self._lock_mode = 'w'
1035
self._lock_count = 1
1036
cache_misses = self._real_repository is None
1037
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1038
for repo in self._fallback_repositories:
1039
# Writes don't affect fallback repos
1041
elif self._lock_mode == 'r':
1042
raise errors.ReadOnlyError(self)
1044
self._lock_count += 1
1045
return self._lock_token or None
1047
def leave_lock_in_place(self):
1048
if not self._lock_token:
1049
raise NotImplementedError(self.leave_lock_in_place)
1050
self._leave_lock = True
1052
def dont_leave_lock_in_place(self):
1053
if not self._lock_token:
1054
raise NotImplementedError(self.dont_leave_lock_in_place)
1055
self._leave_lock = False
1057
def _set_real_repository(self, repository):
1058
"""Set the _real_repository for this repository.
1060
:param repository: The repository to fallback to for non-hpss
1061
implemented operations.
1063
if self._real_repository is not None:
1064
# Replacing an already set real repository.
1065
# We cannot do this [currently] if the repository is locked -
1066
# synchronised state might be lost.
1067
if self.is_locked():
1068
raise AssertionError('_real_repository is already set')
1069
if isinstance(repository, RemoteRepository):
1070
raise AssertionError()
1071
self._real_repository = repository
1072
# three code paths happen here:
1073
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1074
# up stacking. In this case self._fallback_repositories is [], and the
1075
# real repo is already setup. Preserve the real repo and
1076
# RemoteRepository.add_fallback_repository will avoid adding
1078
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1079
# ensure_real is triggered from a branch, the real repository to
1080
# set already has a matching list with separate instances, but
1081
# as they are also RemoteRepositories we don't worry about making the
1082
# lists be identical.
1083
# 3) new servers, RemoteRepository.ensure_real is triggered before
1084
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1085
# and need to populate it.
1086
if (self._fallback_repositories and
1087
len(self._real_repository._fallback_repositories) !=
1088
len(self._fallback_repositories)):
1089
if len(self._real_repository._fallback_repositories):
1090
raise AssertionError(
1091
"cannot cleanly remove existing _fallback_repositories")
1092
for fb in self._fallback_repositories:
1093
self._real_repository.add_fallback_repository(fb)
1094
if self._lock_mode == 'w':
1095
# if we are already locked, the real repository must be able to
1096
# acquire the lock with our token.
1097
self._real_repository.lock_write(self._lock_token)
1098
elif self._lock_mode == 'r':
1099
self._real_repository.lock_read()
1101
def start_write_group(self):
1102
"""Start a write group on the decorated repository.
1104
Smart methods perform operations in a single step so this API
1105
is not really applicable except as a compatibility thunk
1106
for older plugins that don't use e.g. the CommitBuilder
1110
return self._real_repository.start_write_group()
1112
def _unlock(self, token):
1113
path = self.bzrdir._path_for_remote_call(self._client)
1115
# with no token the remote repository is not persistently locked.
1117
err_context = {'token': token}
1118
response = self._call('Repository.unlock', path, token,
1120
if response == ('ok',):
1123
raise errors.UnexpectedSmartServerResponse(response)
1125
@only_raises(errors.LockNotHeld, errors.LockBroken)
1127
if not self._lock_count:
1128
return lock.cant_unlock_not_held(self)
1129
self._lock_count -= 1
1130
if self._lock_count > 0:
1132
self._unstacked_provider.disable_cache()
1133
old_mode = self._lock_mode
1134
self._lock_mode = None
1136
# The real repository is responsible at present for raising an
1137
# exception if it's in an unfinished write group. However, it
1138
# normally will *not* actually remove the lock from disk - that's
1139
# done by the server on receiving the Repository.unlock call.
1140
# This is just to let the _real_repository stay up to date.
1141
if self._real_repository is not None:
1142
self._real_repository.unlock()
1144
# The rpc-level lock should be released even if there was a
1145
# problem releasing the vfs-based lock.
1147
# Only write-locked repositories need to make a remote method
1148
# call to perform the unlock.
1149
old_token = self._lock_token
1150
self._lock_token = None
1151
if not self._leave_lock:
1152
self._unlock(old_token)
1153
# Fallbacks are always 'lock_read()' so we don't pay attention to
1155
for repo in self._fallback_repositories:
1158
def break_lock(self):
1159
# should hand off to the network
1161
return self._real_repository.break_lock()
1163
def _get_tarball(self, compression):
1164
"""Return a TemporaryFile containing a repository tarball.
1166
Returns None if the server does not support sending tarballs.
1169
path = self.bzrdir._path_for_remote_call(self._client)
1171
response, protocol = self._call_expecting_body(
1172
'Repository.tarball', path, compression)
1173
except errors.UnknownSmartMethod:
1174
protocol.cancel_read_body()
1176
if response[0] == 'ok':
1177
# Extract the tarball and return it
1178
t = tempfile.NamedTemporaryFile()
1179
# TODO: rpc layer should read directly into it...
1180
t.write(protocol.read_body_bytes())
1183
raise errors.UnexpectedSmartServerResponse(response)
1185
def sprout(self, to_bzrdir, revision_id=None):
1186
# TODO: Option to control what format is created?
1188
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1190
dest_repo.fetch(self, revision_id=revision_id)
1193
### These methods are just thin shims to the VFS object for now.
1195
def revision_tree(self, revision_id):
1197
return self._real_repository.revision_tree(revision_id)
1199
def get_serializer_format(self):
1201
return self._real_repository.get_serializer_format()
1203
def get_commit_builder(self, branch, parents, config, timestamp=None,
1204
timezone=None, committer=None, revprops=None,
1206
# FIXME: It ought to be possible to call this without immediately
1207
# triggering _ensure_real. For now it's the easiest thing to do.
1209
real_repo = self._real_repository
1210
builder = real_repo.get_commit_builder(branch, parents,
1211
config, timestamp=timestamp, timezone=timezone,
1212
committer=committer, revprops=revprops, revision_id=revision_id)
1215
def add_fallback_repository(self, repository):
1216
"""Add a repository to use for looking up data not held locally.
1218
:param repository: A repository.
1220
if not self._format.supports_external_lookups:
1221
raise errors.UnstackableRepositoryFormat(
1222
self._format.network_name(), self.base)
1223
# We need to accumulate additional repositories here, to pass them in
1226
if self.is_locked():
1227
# We will call fallback.unlock() when we transition to the unlocked
1228
# state, so always add a lock here. If a caller passes us a locked
1229
# repository, they are responsible for unlocking it later.
1230
repository.lock_read()
1231
self._check_fallback_repository(repository)
1232
self._fallback_repositories.append(repository)
1233
# If self._real_repository was parameterised already (e.g. because a
1234
# _real_branch had its get_stacked_on_url method called), then the
1235
# repository to be added may already be in the _real_repositories list.
1236
if self._real_repository is not None:
1237
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1238
self._real_repository._fallback_repositories]
1239
if repository.bzrdir.root_transport.base not in fallback_locations:
1240
self._real_repository.add_fallback_repository(repository)
1242
def _check_fallback_repository(self, repository):
1243
"""Check that this repository can fallback to repository safely.
1245
Raise an error if not.
1247
:param repository: A repository to fallback to.
1249
return _mod_repository.InterRepository._assert_same_model(
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, clean_obsolete_packs=False):
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, clean_obsolete_packs=clean_obsolete_packs)
1619
def revisions(self):
1620
"""Decorate the real repository for now.
1622
In the short term this should become a real object to intercept graph
1625
In the long term a full blown network facility is needed.
1628
return self._real_repository.revisions
1630
def set_make_working_trees(self, new_value):
1632
new_value_str = "True"
1634
new_value_str = "False"
1635
path = self.bzrdir._path_for_remote_call(self._client)
1637
response = self._call(
1638
'Repository.set_make_working_trees', path, new_value_str)
1639
except errors.UnknownSmartMethod:
1641
self._real_repository.set_make_working_trees(new_value)
1643
if response[0] != 'ok':
1644
raise errors.UnexpectedSmartServerResponse(response)
1647
def signatures(self):
1648
"""Decorate the real repository for now.
1650
In the long term a full blown network facility is needed to avoid
1651
creating a real repository object locally.
1654
return self._real_repository.signatures
1657
def sign_revision(self, revision_id, gpg_strategy):
1659
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1663
"""Decorate the real repository for now.
1665
In the long term a full blown network facility is needed to avoid
1666
creating a real repository object locally.
1669
return self._real_repository.texts
1672
def get_revisions(self, revision_ids):
1674
return self._real_repository.get_revisions(revision_ids)
1676
def supports_rich_root(self):
1677
return self._format.rich_root_data
1679
def iter_reverse_revision_history(self, revision_id):
1681
return self._real_repository.iter_reverse_revision_history(revision_id)
1684
def _serializer(self):
1685
return self._format._serializer
1687
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1689
return self._real_repository.store_revision_signature(
1690
gpg_strategy, plaintext, revision_id)
1692
def add_signature_text(self, revision_id, signature):
1694
return self._real_repository.add_signature_text(revision_id, signature)
1696
def has_signature_for_revision_id(self, revision_id):
1698
return self._real_repository.has_signature_for_revision_id(revision_id)
1700
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1702
return self._real_repository.item_keys_introduced_by(revision_ids,
1703
_files_pb=_files_pb)
1705
def revision_graph_can_have_wrong_parents(self):
1706
# The answer depends on the remote repo format.
1708
return self._real_repository.revision_graph_can_have_wrong_parents()
1710
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1712
return self._real_repository._find_inconsistent_revision_parents(
1715
def _check_for_inconsistent_revision_parents(self):
1717
return self._real_repository._check_for_inconsistent_revision_parents()
1719
def _make_parents_provider(self, other=None):
1720
providers = [self._unstacked_provider]
1721
if other is not None:
1722
providers.insert(0, other)
1723
providers.extend(r._make_parents_provider() for r in
1724
self._fallback_repositories)
1725
return graph.StackedParentsProvider(providers)
1727
def _serialise_search_recipe(self, recipe):
1728
"""Serialise a graph search recipe.
1730
:param recipe: A search recipe (start, stop, count).
1731
:return: Serialised bytes.
1733
start_keys = ' '.join(recipe[1])
1734
stop_keys = ' '.join(recipe[2])
1735
count = str(recipe[3])
1736
return '\n'.join((start_keys, stop_keys, count))
1738
def _serialise_search_result(self, search_result):
1739
if isinstance(search_result, graph.PendingAncestryResult):
1740
parts = ['ancestry-of']
1741
parts.extend(search_result.heads)
1743
recipe = search_result.get_recipe()
1744
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1745
return '\n'.join(parts)
1748
path = self.bzrdir._path_for_remote_call(self._client)
1750
response = self._call('PackRepository.autopack', path)
1751
except errors.UnknownSmartMethod:
1753
self._real_repository._pack_collection.autopack()
1756
if response[0] != 'ok':
1757
raise errors.UnexpectedSmartServerResponse(response)
1760
class RemoteStreamSink(repository.StreamSink):
1762
def _insert_real(self, stream, src_format, resume_tokens):
1763
self.target_repo._ensure_real()
1764
sink = self.target_repo._real_repository._get_sink()
1765
result = sink.insert_stream(stream, src_format, resume_tokens)
1767
self.target_repo.autopack()
1770
def insert_stream(self, stream, src_format, resume_tokens):
1771
target = self.target_repo
1772
target._unstacked_provider.missing_keys.clear()
1773
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1774
if target._lock_token:
1775
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1776
lock_args = (target._lock_token or '',)
1778
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1780
client = target._client
1781
medium = client._medium
1782
path = target.bzrdir._path_for_remote_call(client)
1783
# Probe for the verb to use with an empty stream before sending the
1784
# real stream to it. We do this both to avoid the risk of sending a
1785
# large request that is then rejected, and because we don't want to
1786
# implement a way to buffer, rewind, or restart the stream.
1788
for verb, required_version in candidate_calls:
1789
if medium._is_remote_before(required_version):
1792
# We've already done the probing (and set _is_remote_before) on
1793
# a previous insert.
1796
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1798
response = client.call_with_body_stream(
1799
(verb, path, '') + lock_args, byte_stream)
1800
except errors.UnknownSmartMethod:
1801
medium._remember_remote_is_before(required_version)
1807
return self._insert_real(stream, src_format, resume_tokens)
1808
self._last_inv_record = None
1809
self._last_substream = None
1810
if required_version < (1, 19):
1811
# Remote side doesn't support inventory deltas. Wrap the stream to
1812
# make sure we don't send any. If the stream contains inventory
1813
# deltas we'll interrupt the smart insert_stream request and
1815
stream = self._stop_stream_if_inventory_delta(stream)
1816
byte_stream = smart_repo._stream_to_byte_stream(
1818
resume_tokens = ' '.join(resume_tokens)
1819
response = client.call_with_body_stream(
1820
(verb, path, resume_tokens) + lock_args, byte_stream)
1821
if response[0][0] not in ('ok', 'missing-basis'):
1822
raise errors.UnexpectedSmartServerResponse(response)
1823
if self._last_substream is not None:
1824
# The stream included an inventory-delta record, but the remote
1825
# side isn't new enough to support them. So we need to send the
1826
# rest of the stream via VFS.
1827
self.target_repo.refresh_data()
1828
return self._resume_stream_with_vfs(response, src_format)
1829
if response[0][0] == 'missing-basis':
1830
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1831
resume_tokens = tokens
1832
return resume_tokens, set(missing_keys)
1834
self.target_repo.refresh_data()
1837
def _resume_stream_with_vfs(self, response, src_format):
1838
"""Resume sending a stream via VFS, first resending the record and
1839
substream that couldn't be sent via an insert_stream verb.
1841
if response[0][0] == 'missing-basis':
1842
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1843
# Ignore missing_keys, we haven't finished inserting yet
1846
def resume_substream():
1847
# Yield the substream that was interrupted.
1848
for record in self._last_substream:
1850
self._last_substream = None
1851
def resume_stream():
1852
# Finish sending the interrupted substream
1853
yield ('inventory-deltas', resume_substream())
1854
# Then simply continue sending the rest of the stream.
1855
for substream_kind, substream in self._last_stream:
1856
yield substream_kind, substream
1857
return self._insert_real(resume_stream(), src_format, tokens)
1859
def _stop_stream_if_inventory_delta(self, stream):
1860
"""Normally this just lets the original stream pass-through unchanged.
1862
However if any 'inventory-deltas' substream occurs it will stop
1863
streaming, and store the interrupted substream and stream in
1864
self._last_substream and self._last_stream so that the stream can be
1865
resumed by _resume_stream_with_vfs.
1868
stream_iter = iter(stream)
1869
for substream_kind, substream in stream_iter:
1870
if substream_kind == 'inventory-deltas':
1871
self._last_substream = substream
1872
self._last_stream = stream_iter
1875
yield substream_kind, substream
1878
class RemoteStreamSource(repository.StreamSource):
1879
"""Stream data from a remote server."""
1881
def get_stream(self, search):
1882
if (self.from_repository._fallback_repositories and
1883
self.to_format._fetch_order == 'topological'):
1884
return self._real_stream(self.from_repository, search)
1887
repos = [self.from_repository]
1893
repos.extend(repo._fallback_repositories)
1894
sources.append(repo)
1895
return self.missing_parents_chain(search, sources)
1897
def get_stream_for_missing_keys(self, missing_keys):
1898
self.from_repository._ensure_real()
1899
real_repo = self.from_repository._real_repository
1900
real_source = real_repo._get_source(self.to_format)
1901
return real_source.get_stream_for_missing_keys(missing_keys)
1903
def _real_stream(self, repo, search):
1904
"""Get a stream for search from repo.
1906
This never called RemoteStreamSource.get_stream, and is a heler
1907
for RemoteStreamSource._get_stream to allow getting a stream
1908
reliably whether fallback back because of old servers or trying
1909
to stream from a non-RemoteRepository (which the stacked support
1912
source = repo._get_source(self.to_format)
1913
if isinstance(source, RemoteStreamSource):
1915
source = repo._real_repository._get_source(self.to_format)
1916
return source.get_stream(search)
1918
def _get_stream(self, repo, search):
1919
"""Core worker to get a stream from repo for search.
1921
This is used by both get_stream and the stacking support logic. It
1922
deliberately gets a stream for repo which does not need to be
1923
self.from_repository. In the event that repo is not Remote, or
1924
cannot do a smart stream, a fallback is made to the generic
1925
repository._get_stream() interface, via self._real_stream.
1927
In the event of stacking, streams from _get_stream will not
1928
contain all the data for search - this is normal (see get_stream).
1930
:param repo: A repository.
1931
:param search: A search.
1933
# Fallbacks may be non-smart
1934
if not isinstance(repo, RemoteRepository):
1935
return self._real_stream(repo, search)
1936
client = repo._client
1937
medium = client._medium
1938
path = repo.bzrdir._path_for_remote_call(client)
1939
search_bytes = repo._serialise_search_result(search)
1940
args = (path, self.to_format.network_name())
1942
('Repository.get_stream_1.19', (1, 19)),
1943
('Repository.get_stream', (1, 13))]
1945
for verb, version in candidate_verbs:
1946
if medium._is_remote_before(version):
1949
response = repo._call_with_body_bytes_expecting_body(
1950
verb, args, search_bytes)
1951
except errors.UnknownSmartMethod:
1952
medium._remember_remote_is_before(version)
1954
response_tuple, response_handler = response
1958
return self._real_stream(repo, search)
1959
if response_tuple[0] != 'ok':
1960
raise errors.UnexpectedSmartServerResponse(response_tuple)
1961
byte_stream = response_handler.read_streamed_body()
1962
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1963
if src_format.network_name() != repo._format.network_name():
1964
raise AssertionError(
1965
"Mismatched RemoteRepository and stream src %r, %r" % (
1966
src_format.network_name(), repo._format.network_name()))
1969
def missing_parents_chain(self, search, sources):
1970
"""Chain multiple streams together to handle stacking.
1972
:param search: The overall search to satisfy with streams.
1973
:param sources: A list of Repository objects to query.
1975
self.from_serialiser = self.from_repository._format._serializer
1976
self.seen_revs = set()
1977
self.referenced_revs = set()
1978
# If there are heads in the search, or the key count is > 0, we are not
1980
while not search.is_empty() and len(sources) > 1:
1981
source = sources.pop(0)
1982
stream = self._get_stream(source, search)
1983
for kind, substream in stream:
1984
if kind != 'revisions':
1985
yield kind, substream
1987
yield kind, self.missing_parents_rev_handler(substream)
1988
search = search.refine(self.seen_revs, self.referenced_revs)
1989
self.seen_revs = set()
1990
self.referenced_revs = set()
1991
if not search.is_empty():
1992
for kind, stream in self._get_stream(sources[0], search):
1995
def missing_parents_rev_handler(self, substream):
1996
for content in substream:
1997
revision_bytes = content.get_bytes_as('fulltext')
1998
revision = self.from_serialiser.read_revision_from_string(
2000
self.seen_revs.add(content.key[-1])
2001
self.referenced_revs.update(revision.parent_ids)
2005
class RemoteBranchLockableFiles(LockableFiles):
2006
"""A 'LockableFiles' implementation that talks to a smart server.
2008
This is not a public interface class.
2011
def __init__(self, bzrdir, _client):
2012
self.bzrdir = bzrdir
2013
self._client = _client
2014
self._need_find_modes = True
2015
LockableFiles.__init__(
2016
self, bzrdir.get_branch_transport(None),
2017
'lock', lockdir.LockDir)
2019
def _find_modes(self):
2020
# RemoteBranches don't let the client set the mode of control files.
2021
self._dir_mode = None
2022
self._file_mode = None
2025
class RemoteBranchFormat(branch.BranchFormat):
2027
def __init__(self, network_name=None):
2028
super(RemoteBranchFormat, self).__init__()
2029
self._matchingbzrdir = RemoteBzrDirFormat()
2030
self._matchingbzrdir.set_branch_format(self)
2031
self._custom_format = None
2032
self._network_name = network_name
2034
def __eq__(self, other):
2035
return (isinstance(other, RemoteBranchFormat) and
2036
self.__dict__ == other.__dict__)
2038
def _ensure_real(self):
2039
if self._custom_format is None:
2040
self._custom_format = branch.network_format_registry.get(
2043
def get_format_description(self):
2045
return 'Remote: ' + self._custom_format.get_format_description()
2047
def network_name(self):
2048
return self._network_name
2050
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2051
return a_bzrdir.open_branch(name=name,
2052
ignore_fallbacks=ignore_fallbacks)
2054
def _vfs_initialize(self, a_bzrdir, name):
2055
# Initialisation when using a local bzrdir object, or a non-vfs init
2056
# method is not available on the server.
2057
# self._custom_format is always set - the start of initialize ensures
2059
if isinstance(a_bzrdir, RemoteBzrDir):
2060
a_bzrdir._ensure_real()
2061
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2064
# We assume the bzrdir is parameterised; it may not be.
2065
result = self._custom_format.initialize(a_bzrdir, name)
2066
if (isinstance(a_bzrdir, RemoteBzrDir) and
2067
not isinstance(result, RemoteBranch)):
2068
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2072
def initialize(self, a_bzrdir, name=None):
2073
# 1) get the network name to use.
2074
if self._custom_format:
2075
network_name = self._custom_format.network_name()
2077
# Select the current bzrlib default and ask for that.
2078
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2079
reference_format = reference_bzrdir_format.get_branch_format()
2080
self._custom_format = reference_format
2081
network_name = reference_format.network_name()
2082
# Being asked to create on a non RemoteBzrDir:
2083
if not isinstance(a_bzrdir, RemoteBzrDir):
2084
return self._vfs_initialize(a_bzrdir, name=name)
2085
medium = a_bzrdir._client._medium
2086
if medium._is_remote_before((1, 13)):
2087
return self._vfs_initialize(a_bzrdir, name=name)
2088
# Creating on a remote bzr dir.
2089
# 2) try direct creation via RPC
2090
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2091
if name is not None:
2092
# XXX JRV20100304: Support creating colocated branches
2093
raise errors.NoColocatedBranchSupport(self)
2094
verb = 'BzrDir.create_branch'
2096
response = a_bzrdir._call(verb, path, network_name)
2097
except errors.UnknownSmartMethod:
2098
# Fallback - use vfs methods
2099
medium._remember_remote_is_before((1, 13))
2100
return self._vfs_initialize(a_bzrdir, name=name)
2101
if response[0] != 'ok':
2102
raise errors.UnexpectedSmartServerResponse(response)
2103
# Turn the response into a RemoteRepository object.
2104
format = RemoteBranchFormat(network_name=response[1])
2105
repo_format = response_tuple_to_repo_format(response[3:])
2106
if response[2] == '':
2107
repo_bzrdir = a_bzrdir
2109
repo_bzrdir = RemoteBzrDir(
2110
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2112
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2113
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2114
format=format, setup_stacking=False, name=name)
2115
# XXX: We know this is a new branch, so it must have revno 0, revid
2116
# NULL_REVISION. Creating the branch locked would make this be unable
2117
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2118
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2119
return remote_branch
2121
def make_tags(self, branch):
2123
return self._custom_format.make_tags(branch)
2125
def supports_tags(self):
2126
# Remote branches might support tags, but we won't know until we
2127
# access the real remote branch.
2129
return self._custom_format.supports_tags()
2131
def supports_stacking(self):
2133
return self._custom_format.supports_stacking()
2135
def supports_set_append_revisions_only(self):
2137
return self._custom_format.supports_set_append_revisions_only()
2140
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2141
"""Branch stored on a server accessed by HPSS RPC.
2143
At the moment most operations are mapped down to simple file operations.
2146
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2147
_client=None, format=None, setup_stacking=True, name=None):
2148
"""Create a RemoteBranch instance.
2150
:param real_branch: An optional local implementation of the branch
2151
format, usually accessing the data via the VFS.
2152
:param _client: Private parameter for testing.
2153
:param format: A RemoteBranchFormat object, None to create one
2154
automatically. If supplied it should have a network_name already
2156
:param setup_stacking: If True make an RPC call to determine the
2157
stacked (or not) status of the branch. If False assume the branch
2159
:param name: Colocated branch name
2161
# We intentionally don't call the parent class's __init__, because it
2162
# will try to assign to self.tags, which is a property in this subclass.
2163
# And the parent's __init__ doesn't do much anyway.
2164
self.bzrdir = remote_bzrdir
2165
if _client is not None:
2166
self._client = _client
2168
self._client = remote_bzrdir._client
2169
self.repository = remote_repository
2170
if real_branch is not None:
2171
self._real_branch = real_branch
2172
# Give the remote repository the matching real repo.
2173
real_repo = self._real_branch.repository
2174
if isinstance(real_repo, RemoteRepository):
2175
real_repo._ensure_real()
2176
real_repo = real_repo._real_repository
2177
self.repository._set_real_repository(real_repo)
2178
# Give the branch the remote repository to let fast-pathing happen.
2179
self._real_branch.repository = self.repository
2181
self._real_branch = None
2182
# Fill out expected attributes of branch for bzrlib API users.
2183
self._clear_cached_state()
2184
self.base = self.bzrdir.root_transport.base
2186
self._control_files = None
2187
self._lock_mode = None
2188
self._lock_token = None
2189
self._repo_lock_token = None
2190
self._lock_count = 0
2191
self._leave_lock = False
2192
# Setup a format: note that we cannot call _ensure_real until all the
2193
# attributes above are set: This code cannot be moved higher up in this
2196
self._format = RemoteBranchFormat()
2197
if real_branch is not None:
2198
self._format._network_name = \
2199
self._real_branch._format.network_name()
2201
self._format = format
2202
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2203
# branch.open_branch method.
2204
self._real_ignore_fallbacks = not setup_stacking
2205
if not self._format._network_name:
2206
# Did not get from open_branchV2 - old server.
2208
self._format._network_name = \
2209
self._real_branch._format.network_name()
2210
self.tags = self._format.make_tags(self)
2211
# The base class init is not called, so we duplicate this:
2212
hooks = branch.Branch.hooks['open']
2215
self._is_stacked = False
2217
self._setup_stacking()
2219
def _setup_stacking(self):
2220
# configure stacking into the remote repository, by reading it from
2223
fallback_url = self.get_stacked_on_url()
2224
except (errors.NotStacked, errors.UnstackableBranchFormat,
2225
errors.UnstackableRepositoryFormat), e:
2227
self._is_stacked = True
2228
self._activate_fallback_location(fallback_url)
2230
def _get_config(self):
2231
return RemoteBranchConfig(self)
2233
def _get_real_transport(self):
2234
# if we try vfs access, return the real branch's vfs transport
2236
return self._real_branch._transport
2238
_transport = property(_get_real_transport)
2241
return "%s(%s)" % (self.__class__.__name__, self.base)
2245
def _ensure_real(self):
2246
"""Ensure that there is a _real_branch set.
2248
Used before calls to self._real_branch.
2250
if self._real_branch is None:
2251
if not vfs.vfs_enabled():
2252
raise AssertionError('smart server vfs must be enabled '
2253
'to use vfs implementation')
2254
self.bzrdir._ensure_real()
2255
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2256
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2257
if self.repository._real_repository is None:
2258
# Give the remote repository the matching real repo.
2259
real_repo = self._real_branch.repository
2260
if isinstance(real_repo, RemoteRepository):
2261
real_repo._ensure_real()
2262
real_repo = real_repo._real_repository
2263
self.repository._set_real_repository(real_repo)
2264
# Give the real branch the remote repository to let fast-pathing
2266
self._real_branch.repository = self.repository
2267
if self._lock_mode == 'r':
2268
self._real_branch.lock_read()
2269
elif self._lock_mode == 'w':
2270
self._real_branch.lock_write(token=self._lock_token)
2272
def _translate_error(self, err, **context):
2273
self.repository._translate_error(err, branch=self, **context)
2275
def _clear_cached_state(self):
2276
super(RemoteBranch, self)._clear_cached_state()
2277
if self._real_branch is not None:
2278
self._real_branch._clear_cached_state()
2280
def _clear_cached_state_of_remote_branch_only(self):
2281
"""Like _clear_cached_state, but doesn't clear the cache of
2284
This is useful when falling back to calling a method of
2285
self._real_branch that changes state. In that case the underlying
2286
branch changes, so we need to invalidate this RemoteBranch's cache of
2287
it. However, there's no need to invalidate the _real_branch's cache
2288
too, in fact doing so might harm performance.
2290
super(RemoteBranch, self)._clear_cached_state()
2293
def control_files(self):
2294
# Defer actually creating RemoteBranchLockableFiles until its needed,
2295
# because it triggers an _ensure_real that we otherwise might not need.
2296
if self._control_files is None:
2297
self._control_files = RemoteBranchLockableFiles(
2298
self.bzrdir, self._client)
2299
return self._control_files
2301
def _get_checkout_format(self):
2303
return self._real_branch._get_checkout_format()
2305
def get_physical_lock_status(self):
2306
"""See Branch.get_physical_lock_status()."""
2307
# should be an API call to the server, as branches must be lockable.
2309
return self._real_branch.get_physical_lock_status()
2311
def get_stacked_on_url(self):
2312
"""Get the URL this branch is stacked against.
2314
:raises NotStacked: If the branch is not stacked.
2315
:raises UnstackableBranchFormat: If the branch does not support
2317
:raises UnstackableRepositoryFormat: If the repository does not support
2321
# there may not be a repository yet, so we can't use
2322
# self._translate_error, so we can't use self._call either.
2323
response = self._client.call('Branch.get_stacked_on_url',
2324
self._remote_path())
2325
except errors.ErrorFromSmartServer, err:
2326
# there may not be a repository yet, so we can't call through
2327
# its _translate_error
2328
_translate_error(err, branch=self)
2329
except errors.UnknownSmartMethod, err:
2331
return self._real_branch.get_stacked_on_url()
2332
if response[0] != 'ok':
2333
raise errors.UnexpectedSmartServerResponse(response)
2336
def set_stacked_on_url(self, url):
2337
branch.Branch.set_stacked_on_url(self, url)
2339
self._is_stacked = False
2341
self._is_stacked = True
2343
def _vfs_get_tags_bytes(self):
2345
return self._real_branch._get_tags_bytes()
2347
def _get_tags_bytes(self):
2348
medium = self._client._medium
2349
if medium._is_remote_before((1, 13)):
2350
return self._vfs_get_tags_bytes()
2352
response = self._call('Branch.get_tags_bytes', self._remote_path())
2353
except errors.UnknownSmartMethod:
2354
medium._remember_remote_is_before((1, 13))
2355
return self._vfs_get_tags_bytes()
2358
def _vfs_set_tags_bytes(self, bytes):
2360
return self._real_branch._set_tags_bytes(bytes)
2362
def _set_tags_bytes(self, bytes):
2363
medium = self._client._medium
2364
if medium._is_remote_before((1, 18)):
2365
self._vfs_set_tags_bytes(bytes)
2369
self._remote_path(), self._lock_token, self._repo_lock_token)
2370
response = self._call_with_body_bytes(
2371
'Branch.set_tags_bytes', args, bytes)
2372
except errors.UnknownSmartMethod:
2373
medium._remember_remote_is_before((1, 18))
2374
self._vfs_set_tags_bytes(bytes)
2376
def lock_read(self):
2377
self.repository.lock_read()
2378
if not self._lock_mode:
2379
self._note_lock('r')
2380
self._lock_mode = 'r'
2381
self._lock_count = 1
2382
if self._real_branch is not None:
2383
self._real_branch.lock_read()
2385
self._lock_count += 1
2387
def _remote_lock_write(self, token):
2389
branch_token = repo_token = ''
2391
branch_token = token
2392
repo_token = self.repository.lock_write()
2393
self.repository.unlock()
2394
err_context = {'token': token}
2395
response = self._call(
2396
'Branch.lock_write', self._remote_path(), branch_token,
2397
repo_token or '', **err_context)
2398
if response[0] != 'ok':
2399
raise errors.UnexpectedSmartServerResponse(response)
2400
ok, branch_token, repo_token = response
2401
return branch_token, repo_token
2403
def lock_write(self, token=None):
2404
if not self._lock_mode:
2405
self._note_lock('w')
2406
# Lock the branch and repo in one remote call.
2407
remote_tokens = self._remote_lock_write(token)
2408
self._lock_token, self._repo_lock_token = remote_tokens
2409
if not self._lock_token:
2410
raise SmartProtocolError('Remote server did not return a token!')
2411
# Tell the self.repository object that it is locked.
2412
self.repository.lock_write(
2413
self._repo_lock_token, _skip_rpc=True)
2415
if self._real_branch is not None:
2416
self._real_branch.lock_write(token=self._lock_token)
2417
if token is not None:
2418
self._leave_lock = True
2420
self._leave_lock = False
2421
self._lock_mode = 'w'
2422
self._lock_count = 1
2423
elif self._lock_mode == 'r':
2424
raise errors.ReadOnlyTransaction
2426
if token is not None:
2427
# A token was given to lock_write, and we're relocking, so
2428
# check that the given token actually matches the one we
2430
if token != self._lock_token:
2431
raise errors.TokenMismatch(token, self._lock_token)
2432
self._lock_count += 1
2433
# Re-lock the repository too.
2434
self.repository.lock_write(self._repo_lock_token)
2435
return self._lock_token or None
2437
def _unlock(self, branch_token, repo_token):
2438
err_context = {'token': str((branch_token, repo_token))}
2439
response = self._call(
2440
'Branch.unlock', self._remote_path(), branch_token,
2441
repo_token or '', **err_context)
2442
if response == ('ok',):
2444
raise errors.UnexpectedSmartServerResponse(response)
2446
@only_raises(errors.LockNotHeld, errors.LockBroken)
2449
self._lock_count -= 1
2450
if not self._lock_count:
2451
self._clear_cached_state()
2452
mode = self._lock_mode
2453
self._lock_mode = None
2454
if self._real_branch is not None:
2455
if (not self._leave_lock and mode == 'w' and
2456
self._repo_lock_token):
2457
# If this RemoteBranch will remove the physical lock
2458
# for the repository, make sure the _real_branch
2459
# doesn't do it first. (Because the _real_branch's
2460
# repository is set to be the RemoteRepository.)
2461
self._real_branch.repository.leave_lock_in_place()
2462
self._real_branch.unlock()
2464
# Only write-locked branched need to make a remote method
2465
# call to perform the unlock.
2467
if not self._lock_token:
2468
raise AssertionError('Locked, but no token!')
2469
branch_token = self._lock_token
2470
repo_token = self._repo_lock_token
2471
self._lock_token = None
2472
self._repo_lock_token = None
2473
if not self._leave_lock:
2474
self._unlock(branch_token, repo_token)
2476
self.repository.unlock()
2478
def break_lock(self):
2480
return self._real_branch.break_lock()
2482
def leave_lock_in_place(self):
2483
if not self._lock_token:
2484
raise NotImplementedError(self.leave_lock_in_place)
2485
self._leave_lock = True
2487
def dont_leave_lock_in_place(self):
2488
if not self._lock_token:
2489
raise NotImplementedError(self.dont_leave_lock_in_place)
2490
self._leave_lock = False
2493
def get_rev_id(self, revno, history=None):
2495
return _mod_revision.NULL_REVISION
2496
last_revision_info = self.last_revision_info()
2497
ok, result = self.repository.get_rev_id_for_revno(
2498
revno, last_revision_info)
2501
missing_parent = result[1]
2502
# Either the revision named by the server is missing, or its parent
2503
# is. Call get_parent_map to determine which, so that we report a
2505
parent_map = self.repository.get_parent_map([missing_parent])
2506
if missing_parent in parent_map:
2507
missing_parent = parent_map[missing_parent]
2508
raise errors.RevisionNotPresent(missing_parent, self.repository)
2510
def _last_revision_info(self):
2511
response = self._call('Branch.last_revision_info', self._remote_path())
2512
if response[0] != 'ok':
2513
raise SmartProtocolError('unexpected response code %s' % (response,))
2514
revno = int(response[1])
2515
last_revision = response[2]
2516
return (revno, last_revision)
2518
def _gen_revision_history(self):
2519
"""See Branch._gen_revision_history()."""
2520
if self._is_stacked:
2522
return self._real_branch._gen_revision_history()
2523
response_tuple, response_handler = self._call_expecting_body(
2524
'Branch.revision_history', self._remote_path())
2525
if response_tuple[0] != 'ok':
2526
raise errors.UnexpectedSmartServerResponse(response_tuple)
2527
result = response_handler.read_body_bytes().split('\x00')
2532
def _remote_path(self):
2533
return self.bzrdir._path_for_remote_call(self._client)
2535
def _set_last_revision_descendant(self, revision_id, other_branch,
2536
allow_diverged=False, allow_overwrite_descendant=False):
2537
# This performs additional work to meet the hook contract; while its
2538
# undesirable, we have to synthesise the revno to call the hook, and
2539
# not calling the hook is worse as it means changes can't be prevented.
2540
# Having calculated this though, we can't just call into
2541
# set_last_revision_info as a simple call, because there is a set_rh
2542
# hook that some folk may still be using.
2543
old_revno, old_revid = self.last_revision_info()
2544
history = self._lefthand_history(revision_id)
2545
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2546
err_context = {'other_branch': other_branch}
2547
response = self._call('Branch.set_last_revision_ex',
2548
self._remote_path(), self._lock_token, self._repo_lock_token,
2549
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2551
self._clear_cached_state()
2552
if len(response) != 3 and response[0] != 'ok':
2553
raise errors.UnexpectedSmartServerResponse(response)
2554
new_revno, new_revision_id = response[1:]
2555
self._last_revision_info_cache = new_revno, new_revision_id
2556
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2557
if self._real_branch is not None:
2558
cache = new_revno, new_revision_id
2559
self._real_branch._last_revision_info_cache = cache
2561
def _set_last_revision(self, revision_id):
2562
old_revno, old_revid = self.last_revision_info()
2563
# This performs additional work to meet the hook contract; while its
2564
# undesirable, we have to synthesise the revno to call the hook, and
2565
# not calling the hook is worse as it means changes can't be prevented.
2566
# Having calculated this though, we can't just call into
2567
# set_last_revision_info as a simple call, because there is a set_rh
2568
# hook that some folk may still be using.
2569
history = self._lefthand_history(revision_id)
2570
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2571
self._clear_cached_state()
2572
response = self._call('Branch.set_last_revision',
2573
self._remote_path(), self._lock_token, self._repo_lock_token,
2575
if response != ('ok',):
2576
raise errors.UnexpectedSmartServerResponse(response)
2577
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2580
def set_revision_history(self, rev_history):
2581
# Send just the tip revision of the history; the server will generate
2582
# the full history from that. If the revision doesn't exist in this
2583
# branch, NoSuchRevision will be raised.
2584
if rev_history == []:
2587
rev_id = rev_history[-1]
2588
self._set_last_revision(rev_id)
2589
for hook in branch.Branch.hooks['set_rh']:
2590
hook(self, rev_history)
2591
self._cache_revision_history(rev_history)
2593
def _get_parent_location(self):
2594
medium = self._client._medium
2595
if medium._is_remote_before((1, 13)):
2596
return self._vfs_get_parent_location()
2598
response = self._call('Branch.get_parent', self._remote_path())
2599
except errors.UnknownSmartMethod:
2600
medium._remember_remote_is_before((1, 13))
2601
return self._vfs_get_parent_location()
2602
if len(response) != 1:
2603
raise errors.UnexpectedSmartServerResponse(response)
2604
parent_location = response[0]
2605
if parent_location == '':
2607
return parent_location
2609
def _vfs_get_parent_location(self):
2611
return self._real_branch._get_parent_location()
2613
def _set_parent_location(self, url):
2614
medium = self._client._medium
2615
if medium._is_remote_before((1, 15)):
2616
return self._vfs_set_parent_location(url)
2618
call_url = url or ''
2619
if type(call_url) is not str:
2620
raise AssertionError('url must be a str or None (%s)' % url)
2621
response = self._call('Branch.set_parent_location',
2622
self._remote_path(), self._lock_token, self._repo_lock_token,
2624
except errors.UnknownSmartMethod:
2625
medium._remember_remote_is_before((1, 15))
2626
return self._vfs_set_parent_location(url)
2628
raise errors.UnexpectedSmartServerResponse(response)
2630
def _vfs_set_parent_location(self, url):
2632
return self._real_branch._set_parent_location(url)
2635
def pull(self, source, overwrite=False, stop_revision=None,
2637
self._clear_cached_state_of_remote_branch_only()
2639
return self._real_branch.pull(
2640
source, overwrite=overwrite, stop_revision=stop_revision,
2641
_override_hook_target=self, **kwargs)
2644
def push(self, target, overwrite=False, stop_revision=None):
2646
return self._real_branch.push(
2647
target, overwrite=overwrite, stop_revision=stop_revision,
2648
_override_hook_source_branch=self)
2650
def is_locked(self):
2651
return self._lock_count >= 1
2654
def revision_id_to_revno(self, revision_id):
2656
return self._real_branch.revision_id_to_revno(revision_id)
2659
def set_last_revision_info(self, revno, revision_id):
2660
# XXX: These should be returned by the set_last_revision_info verb
2661
old_revno, old_revid = self.last_revision_info()
2662
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2663
revision_id = ensure_null(revision_id)
2665
response = self._call('Branch.set_last_revision_info',
2666
self._remote_path(), self._lock_token, self._repo_lock_token,
2667
str(revno), revision_id)
2668
except errors.UnknownSmartMethod:
2670
self._clear_cached_state_of_remote_branch_only()
2671
self._real_branch.set_last_revision_info(revno, revision_id)
2672
self._last_revision_info_cache = revno, revision_id
2674
if response == ('ok',):
2675
self._clear_cached_state()
2676
self._last_revision_info_cache = revno, revision_id
2677
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2678
# Update the _real_branch's cache too.
2679
if self._real_branch is not None:
2680
cache = self._last_revision_info_cache
2681
self._real_branch._last_revision_info_cache = cache
2683
raise errors.UnexpectedSmartServerResponse(response)
2686
def generate_revision_history(self, revision_id, last_rev=None,
2688
medium = self._client._medium
2689
if not medium._is_remote_before((1, 6)):
2690
# Use a smart method for 1.6 and above servers
2692
self._set_last_revision_descendant(revision_id, other_branch,
2693
allow_diverged=True, allow_overwrite_descendant=True)
2695
except errors.UnknownSmartMethod:
2696
medium._remember_remote_is_before((1, 6))
2697
self._clear_cached_state_of_remote_branch_only()
2698
self.set_revision_history(self._lefthand_history(revision_id,
2699
last_rev=last_rev,other_branch=other_branch))
2701
def set_push_location(self, location):
2703
return self._real_branch.set_push_location(location)
2706
class RemoteConfig(object):
2707
"""A Config that reads and writes from smart verbs.
2709
It is a low-level object that considers config data to be name/value pairs
2710
that may be associated with a section. Assigning meaning to the these
2711
values is done at higher levels like bzrlib.config.TreeConfig.
2714
def get_option(self, name, section=None, default=None):
2715
"""Return the value associated with a named option.
2717
:param name: The name of the value
2718
:param section: The section the option is in (if any)
2719
:param default: The value to return if the value is not set
2720
:return: The value or default value
2723
configobj = self._get_configobj()
2725
section_obj = configobj
2728
section_obj = configobj[section]
2731
return section_obj.get(name, default)
2732
except errors.UnknownSmartMethod:
2733
return self._vfs_get_option(name, section, default)
2735
def _response_to_configobj(self, response):
2736
if len(response[0]) and response[0][0] != 'ok':
2737
raise errors.UnexpectedSmartServerResponse(response)
2738
lines = response[1].read_body_bytes().splitlines()
2739
return config.ConfigObj(lines, encoding='utf-8')
2742
class RemoteBranchConfig(RemoteConfig):
2743
"""A RemoteConfig for Branches."""
2745
def __init__(self, branch):
2746
self._branch = branch
2748
def _get_configobj(self):
2749
path = self._branch._remote_path()
2750
response = self._branch._client.call_expecting_body(
2751
'Branch.get_config_file', path)
2752
return self._response_to_configobj(response)
2754
def set_option(self, value, name, section=None):
2755
"""Set the value associated with a named option.
2757
:param value: The value to set
2758
:param name: The name of the value to set
2759
:param section: The section the option is in (if any)
2761
medium = self._branch._client._medium
2762
if medium._is_remote_before((1, 14)):
2763
return self._vfs_set_option(value, name, section)
2765
path = self._branch._remote_path()
2766
response = self._branch._client.call('Branch.set_config_option',
2767
path, self._branch._lock_token, self._branch._repo_lock_token,
2768
value.encode('utf8'), name, section or '')
2769
except errors.UnknownSmartMethod:
2770
medium._remember_remote_is_before((1, 14))
2771
return self._vfs_set_option(value, name, section)
2773
raise errors.UnexpectedSmartServerResponse(response)
2775
def _real_object(self):
2776
self._branch._ensure_real()
2777
return self._branch._real_branch
2779
def _vfs_set_option(self, value, name, section=None):
2780
return self._real_object()._get_config().set_option(
2781
value, name, section)
2784
class RemoteBzrDirConfig(RemoteConfig):
2785
"""A RemoteConfig for BzrDirs."""
2787
def __init__(self, bzrdir):
2788
self._bzrdir = bzrdir
2790
def _get_configobj(self):
2791
medium = self._bzrdir._client._medium
2792
verb = 'BzrDir.get_config_file'
2793
if medium._is_remote_before((1, 15)):
2794
raise errors.UnknownSmartMethod(verb)
2795
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2796
response = self._bzrdir._call_expecting_body(
2798
return self._response_to_configobj(response)
2800
def _vfs_get_option(self, name, section, default):
2801
return self._real_object()._get_config().get_option(
2802
name, section, default)
2804
def set_option(self, value, name, section=None):
2805
"""Set the value associated with a named option.
2807
:param value: The value to set
2808
:param name: The name of the value to set
2809
:param section: The section the option is in (if any)
2811
return self._real_object()._get_config().set_option(
2812
value, name, section)
2814
def _real_object(self):
2815
self._bzrdir._ensure_real()
2816
return self._bzrdir._real_bzrdir
2820
def _extract_tar(tar, to_dir):
2821
"""Extract all the contents of a tarfile object.
2823
A replacement for extractall, which is not present in python2.4
2826
tar.extract(tarinfo, to_dir)
2829
def _translate_error(err, **context):
2830
"""Translate an ErrorFromSmartServer into a more useful error.
2832
Possible context keys:
2840
If the error from the server doesn't match a known pattern, then
2841
UnknownErrorFromSmartServer is raised.
2845
return context[name]
2846
except KeyError, key_err:
2847
mutter('Missing key %r in context %r', key_err.args[0], context)
2850
"""Get the path from the context if present, otherwise use first error
2854
return context['path']
2855
except KeyError, key_err:
2857
return err.error_args[0]
2858
except IndexError, idx_err:
2860
'Missing key %r in context %r', key_err.args[0], context)
2863
if err.error_verb == 'IncompatibleRepositories':
2864
raise errors.IncompatibleRepositories(err.error_args[0],
2865
err.error_args[1], err.error_args[2])
2866
elif err.error_verb == 'NoSuchRevision':
2867
raise NoSuchRevision(find('branch'), err.error_args[0])
2868
elif err.error_verb == 'nosuchrevision':
2869
raise NoSuchRevision(find('repository'), err.error_args[0])
2870
elif err.error_verb == 'nobranch':
2871
if len(err.error_args) >= 1:
2872
extra = err.error_args[0]
2875
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2877
elif err.error_verb == 'norepository':
2878
raise errors.NoRepositoryPresent(find('bzrdir'))
2879
elif err.error_verb == 'LockContention':
2880
raise errors.LockContention('(remote lock)')
2881
elif err.error_verb == 'UnlockableTransport':
2882
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2883
elif err.error_verb == 'LockFailed':
2884
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2885
elif err.error_verb == 'TokenMismatch':
2886
raise errors.TokenMismatch(find('token'), '(remote token)')
2887
elif err.error_verb == 'Diverged':
2888
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2889
elif err.error_verb == 'TipChangeRejected':
2890
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2891
elif err.error_verb == 'UnstackableBranchFormat':
2892
raise errors.UnstackableBranchFormat(*err.error_args)
2893
elif err.error_verb == 'UnstackableRepositoryFormat':
2894
raise errors.UnstackableRepositoryFormat(*err.error_args)
2895
elif err.error_verb == 'NotStacked':
2896
raise errors.NotStacked(branch=find('branch'))
2897
elif err.error_verb == 'PermissionDenied':
2899
if len(err.error_args) >= 2:
2900
extra = err.error_args[1]
2903
raise errors.PermissionDenied(path, extra=extra)
2904
elif err.error_verb == 'ReadError':
2906
raise errors.ReadError(path)
2907
elif err.error_verb == 'NoSuchFile':
2909
raise errors.NoSuchFile(path)
2910
elif err.error_verb == 'FileExists':
2911
raise errors.FileExists(err.error_args[0])
2912
elif err.error_verb == 'DirectoryNotEmpty':
2913
raise errors.DirectoryNotEmpty(err.error_args[0])
2914
elif err.error_verb == 'ShortReadvError':
2915
args = err.error_args
2916
raise errors.ShortReadvError(
2917
args[0], int(args[1]), int(args[2]), int(args[3]))
2918
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2919
encoding = str(err.error_args[0]) # encoding must always be a string
2920
val = err.error_args[1]
2921
start = int(err.error_args[2])
2922
end = int(err.error_args[3])
2923
reason = str(err.error_args[4]) # reason must always be a string
2924
if val.startswith('u:'):
2925
val = val[2:].decode('utf-8')
2926
elif val.startswith('s:'):
2927
val = val[2:].decode('base64')
2928
if err.error_verb == 'UnicodeDecodeError':
2929
raise UnicodeDecodeError(encoding, val, start, end, reason)
2930
elif err.error_verb == 'UnicodeEncodeError':
2931
raise UnicodeEncodeError(encoding, val, start, end, reason)
2932
elif err.error_verb == 'ReadOnlyError':
2933
raise errors.TransportNotPossible('readonly transport')
2934
raise errors.UnknownErrorFromSmartServer(err)