1
# Copyright (C) 2006-2010 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
31
revision as _mod_revision,
34
from bzrlib.branch import BranchReferenceFormat
35
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
36
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
37
from bzrlib.errors import (
41
from bzrlib.lockable_files import LockableFiles
42
from bzrlib.smart import client, vfs, repository as smart_repo
43
from bzrlib.revision import ensure_null, NULL_REVISION
44
from bzrlib.trace import mutter, note, warning
47
class _RpcHelper(object):
48
"""Mixin class that helps with issuing RPCs."""
50
def _call(self, method, *args, **err_context):
52
return self._client.call(method, *args)
53
except errors.ErrorFromSmartServer, err:
54
self._translate_error(err, **err_context)
56
def _call_expecting_body(self, method, *args, **err_context):
58
return self._client.call_expecting_body(method, *args)
59
except errors.ErrorFromSmartServer, err:
60
self._translate_error(err, **err_context)
62
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
64
return self._client.call_with_body_bytes(method, args, body_bytes)
65
except errors.ErrorFromSmartServer, err:
66
self._translate_error(err, **err_context)
68
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
71
return self._client.call_with_body_bytes_expecting_body(
72
method, args, body_bytes)
73
except errors.ErrorFromSmartServer, err:
74
self._translate_error(err, **err_context)
77
def response_tuple_to_repo_format(response):
78
"""Convert a response tuple describing a repository format to a format."""
79
format = RemoteRepositoryFormat()
80
format._rich_root_data = (response[0] == 'yes')
81
format._supports_tree_reference = (response[1] == 'yes')
82
format._supports_external_lookups = (response[2] == 'yes')
83
format._network_name = response[3]
87
# Note: RemoteBzrDirFormat is in bzrdir.py
89
class RemoteBzrDir(BzrDir, _RpcHelper):
90
"""Control directory on a remote server, accessed via bzr:// or similar."""
92
def __init__(self, transport, format, _client=None, _force_probe=False):
93
"""Construct a RemoteBzrDir.
95
:param _client: Private parameter for testing. Disables probing and the
98
BzrDir.__init__(self, transport, format)
99
# this object holds a delegated bzrdir that uses file-level operations
100
# to talk to the other side
101
self._real_bzrdir = None
102
self._has_working_tree = None
103
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
104
# create_branch for details.
105
self._next_open_branch_result = None
108
medium = transport.get_smart_medium()
109
self._client = client._SmartClient(medium)
111
self._client = _client
118
return '%s(%r)' % (self.__class__.__name__, self._client)
120
def _probe_bzrdir(self):
121
medium = self._client._medium
122
path = self._path_for_remote_call(self._client)
123
if medium._is_remote_before((2, 1)):
127
self._rpc_open_2_1(path)
129
except errors.UnknownSmartMethod:
130
medium._remember_remote_is_before((2, 1))
133
def _rpc_open_2_1(self, path):
134
response = self._call('BzrDir.open_2.1', path)
135
if response == ('no',):
136
raise errors.NotBranchError(path=self.root_transport.base)
137
elif response[0] == 'yes':
138
if response[1] == 'yes':
139
self._has_working_tree = True
140
elif response[1] == 'no':
141
self._has_working_tree = False
143
raise errors.UnexpectedSmartServerResponse(response)
145
raise errors.UnexpectedSmartServerResponse(response)
147
def _rpc_open(self, path):
148
response = self._call('BzrDir.open', path)
149
if response not in [('yes',), ('no',)]:
150
raise errors.UnexpectedSmartServerResponse(response)
151
if response == ('no',):
152
raise errors.NotBranchError(path=self.root_transport.base)
154
def _ensure_real(self):
155
"""Ensure that there is a _real_bzrdir set.
157
Used before calls to self._real_bzrdir.
159
if not self._real_bzrdir:
160
if 'hpssvfs' in debug.debug_flags:
162
warning('VFS BzrDir access triggered\n%s',
163
''.join(traceback.format_stack()))
164
self._real_bzrdir = BzrDir.open_from_transport(
165
self.root_transport, _server_formats=False)
166
self._format._network_name = \
167
self._real_bzrdir._format.network_name()
169
def _translate_error(self, err, **context):
170
_translate_error(err, bzrdir=self, **context)
172
def break_lock(self):
173
# Prevent aliasing problems in the next_open_branch_result cache.
174
# See create_branch for rationale.
175
self._next_open_branch_result = None
176
return BzrDir.break_lock(self)
178
def _vfs_cloning_metadir(self, require_stacking=False):
180
return self._real_bzrdir.cloning_metadir(
181
require_stacking=require_stacking)
183
def cloning_metadir(self, require_stacking=False):
184
medium = self._client._medium
185
if medium._is_remote_before((1, 13)):
186
return self._vfs_cloning_metadir(require_stacking=require_stacking)
187
verb = 'BzrDir.cloning_metadir'
192
path = self._path_for_remote_call(self._client)
194
response = self._call(verb, path, stacking)
195
except errors.UnknownSmartMethod:
196
medium._remember_remote_is_before((1, 13))
197
return self._vfs_cloning_metadir(require_stacking=require_stacking)
198
except errors.UnknownErrorFromSmartServer, err:
199
if err.error_tuple != ('BranchReference',):
201
# We need to resolve the branch reference to determine the
202
# cloning_metadir. This causes unnecessary RPCs to open the
203
# referenced branch (and bzrdir, etc) but only when the caller
204
# didn't already resolve the branch reference.
205
referenced_branch = self.open_branch()
206
return referenced_branch.bzrdir.cloning_metadir()
207
if len(response) != 3:
208
raise errors.UnexpectedSmartServerResponse(response)
209
control_name, repo_name, branch_info = response
210
if len(branch_info) != 2:
211
raise errors.UnexpectedSmartServerResponse(response)
212
branch_ref, branch_name = branch_info
213
format = bzrdir.network_format_registry.get(control_name)
215
format.repository_format = repository.network_format_registry.get(
217
if branch_ref == 'ref':
218
# XXX: we need possible_transports here to avoid reopening the
219
# connection to the referenced location
220
ref_bzrdir = BzrDir.open(branch_name)
221
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
222
format.set_branch_format(branch_format)
223
elif branch_ref == 'branch':
225
format.set_branch_format(
226
branch.network_format_registry.get(branch_name))
228
raise errors.UnexpectedSmartServerResponse(response)
231
def create_repository(self, shared=False):
232
# as per meta1 formats - just delegate to the format object which may
234
result = self._format.repository_format.initialize(self, shared)
235
if not isinstance(result, RemoteRepository):
236
return self.open_repository()
240
def destroy_repository(self):
241
"""See BzrDir.destroy_repository"""
243
self._real_bzrdir.destroy_repository()
245
def create_branch(self, name=None):
247
raise errors.NoColocatedBranchSupport(self)
248
# as per meta1 formats - just delegate to the format object which may
250
real_branch = self._format.get_branch_format().initialize(self)
251
if not isinstance(real_branch, RemoteBranch):
252
result = RemoteBranch(self, self.find_repository(), real_branch)
255
# BzrDir.clone_on_transport() uses the result of create_branch but does
256
# not return it to its callers; we save approximately 8% of our round
257
# trips by handing the branch we created back to the first caller to
258
# open_branch rather than probing anew. Long term we need a API in
259
# bzrdir that doesn't discard result objects (like result_branch).
261
self._next_open_branch_result = result
264
def destroy_branch(self, name=None):
265
"""See BzrDir.destroy_branch"""
267
self._real_bzrdir.destroy_branch(name=name)
268
self._next_open_branch_result = None
270
def create_workingtree(self, revision_id=None, from_branch=None):
271
raise errors.NotLocalUrl(self.transport.base)
273
def find_branch_format(self):
274
"""Find the branch 'format' for this bzrdir.
276
This might be a synthetic object for e.g. RemoteBranch and SVN.
278
b = self.open_branch()
281
def get_branch_reference(self):
282
"""See BzrDir.get_branch_reference()."""
283
response = self._get_branch_reference()
284
if response[0] == 'ref':
289
def _get_branch_reference(self):
290
path = self._path_for_remote_call(self._client)
291
medium = self._client._medium
293
('BzrDir.open_branchV3', (2, 1)),
294
('BzrDir.open_branchV2', (1, 13)),
295
('BzrDir.open_branch', None),
297
for verb, required_version in candidate_calls:
298
if required_version and medium._is_remote_before(required_version):
301
response = self._call(verb, path)
302
except errors.UnknownSmartMethod:
303
if required_version is None:
305
medium._remember_remote_is_before(required_version)
308
if verb == 'BzrDir.open_branch':
309
if response[0] != 'ok':
310
raise errors.UnexpectedSmartServerResponse(response)
311
if response[1] != '':
312
return ('ref', response[1])
314
return ('branch', '')
315
if response[0] not in ('ref', 'branch'):
316
raise errors.UnexpectedSmartServerResponse(response)
319
def _get_tree_branch(self):
320
"""See BzrDir._get_tree_branch()."""
321
return None, self.open_branch()
323
def open_branch(self, name=None, unsupported=False,
324
ignore_fallbacks=False):
326
raise NotImplementedError('unsupported flag support not implemented yet.')
328
raise errors.NoColocatedBranchSupport(self)
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, _found=True, location=response[1],
339
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)
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):
430
return self._real_bzrdir.get_branch_transport(branch_format)
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)
908
def gather_stats(self, revid=None, committers=None):
909
"""See Repository.gather_stats()."""
910
path = self.bzrdir._path_for_remote_call(self._client)
911
# revid can be None to indicate no revisions, not just NULL_REVISION
912
if revid is None or revision.is_null(revid):
916
if committers is None or not committers:
917
fmt_committers = 'no'
919
fmt_committers = 'yes'
920
response_tuple, response_handler = self._call_expecting_body(
921
'Repository.gather_stats', path, fmt_revid, fmt_committers)
922
if response_tuple[0] != 'ok':
923
raise errors.UnexpectedSmartServerResponse(response_tuple)
925
body = response_handler.read_body_bytes()
927
for line in body.split('\n'):
930
key, val_text = line.split(':')
931
if key in ('revisions', 'size', 'committers'):
932
result[key] = int(val_text)
933
elif key in ('firstrev', 'latestrev'):
934
values = val_text.split(' ')[1:]
935
result[key] = (float(values[0]), long(values[1]))
939
def find_branches(self, using=False):
940
"""See Repository.find_branches()."""
941
# should be an API call to the server.
943
return self._real_repository.find_branches(using=using)
945
def get_physical_lock_status(self):
946
"""See Repository.get_physical_lock_status()."""
947
# should be an API call to the server.
949
return self._real_repository.get_physical_lock_status()
951
def is_in_write_group(self):
952
"""Return True if there is an open write group.
954
write groups are only applicable locally for the smart server..
956
if self._real_repository:
957
return self._real_repository.is_in_write_group()
960
return self._lock_count >= 1
963
"""See Repository.is_shared()."""
964
path = self.bzrdir._path_for_remote_call(self._client)
965
response = self._call('Repository.is_shared', path)
966
if response[0] not in ('yes', 'no'):
967
raise SmartProtocolError('unexpected response code %s' % (response,))
968
return response[0] == 'yes'
970
def is_write_locked(self):
971
return self._lock_mode == 'w'
973
def _warn_if_deprecated(self, branch=None):
974
# If we have a real repository, the check will be done there, if we
975
# don't the check will be done remotely.
979
# wrong eventually - want a local lock cache context
980
if not self._lock_mode:
982
self._lock_mode = 'r'
984
self._unstacked_provider.enable_cache(cache_misses=True)
985
if self._real_repository is not None:
986
self._real_repository.lock_read()
987
for repo in self._fallback_repositories:
990
self._lock_count += 1
992
def _remote_lock_write(self, token):
993
path = self.bzrdir._path_for_remote_call(self._client)
996
err_context = {'token': token}
997
response = self._call('Repository.lock_write', path, token,
999
if response[0] == 'ok':
1000
ok, token = response
1003
raise errors.UnexpectedSmartServerResponse(response)
1005
def lock_write(self, token=None, _skip_rpc=False):
1006
if not self._lock_mode:
1007
self._note_lock('w')
1009
if self._lock_token is not None:
1010
if token != self._lock_token:
1011
raise errors.TokenMismatch(token, self._lock_token)
1012
self._lock_token = token
1014
self._lock_token = self._remote_lock_write(token)
1015
# if self._lock_token is None, then this is something like packs or
1016
# svn where we don't get to lock the repo, or a weave style repository
1017
# where we cannot lock it over the wire and attempts to do so will
1019
if self._real_repository is not None:
1020
self._real_repository.lock_write(token=self._lock_token)
1021
if token is not None:
1022
self._leave_lock = True
1024
self._leave_lock = False
1025
self._lock_mode = 'w'
1026
self._lock_count = 1
1027
cache_misses = self._real_repository is None
1028
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1029
for repo in self._fallback_repositories:
1030
# Writes don't affect fallback repos
1032
elif self._lock_mode == 'r':
1033
raise errors.ReadOnlyError(self)
1035
self._lock_count += 1
1036
return self._lock_token or None
1038
def leave_lock_in_place(self):
1039
if not self._lock_token:
1040
raise NotImplementedError(self.leave_lock_in_place)
1041
self._leave_lock = True
1043
def dont_leave_lock_in_place(self):
1044
if not self._lock_token:
1045
raise NotImplementedError(self.dont_leave_lock_in_place)
1046
self._leave_lock = False
1048
def _set_real_repository(self, repository):
1049
"""Set the _real_repository for this repository.
1051
:param repository: The repository to fallback to for non-hpss
1052
implemented operations.
1054
if self._real_repository is not None:
1055
# Replacing an already set real repository.
1056
# We cannot do this [currently] if the repository is locked -
1057
# synchronised state might be lost.
1058
if self.is_locked():
1059
raise AssertionError('_real_repository is already set')
1060
if isinstance(repository, RemoteRepository):
1061
raise AssertionError()
1062
self._real_repository = repository
1063
# three code paths happen here:
1064
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1065
# up stacking. In this case self._fallback_repositories is [], and the
1066
# real repo is already setup. Preserve the real repo and
1067
# RemoteRepository.add_fallback_repository will avoid adding
1069
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1070
# ensure_real is triggered from a branch, the real repository to
1071
# set already has a matching list with separate instances, but
1072
# as they are also RemoteRepositories we don't worry about making the
1073
# lists be identical.
1074
# 3) new servers, RemoteRepository.ensure_real is triggered before
1075
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1076
# and need to populate it.
1077
if (self._fallback_repositories and
1078
len(self._real_repository._fallback_repositories) !=
1079
len(self._fallback_repositories)):
1080
if len(self._real_repository._fallback_repositories):
1081
raise AssertionError(
1082
"cannot cleanly remove existing _fallback_repositories")
1083
for fb in self._fallback_repositories:
1084
self._real_repository.add_fallback_repository(fb)
1085
if self._lock_mode == 'w':
1086
# if we are already locked, the real repository must be able to
1087
# acquire the lock with our token.
1088
self._real_repository.lock_write(self._lock_token)
1089
elif self._lock_mode == 'r':
1090
self._real_repository.lock_read()
1092
def start_write_group(self):
1093
"""Start a write group on the decorated repository.
1095
Smart methods perform operations in a single step so this API
1096
is not really applicable except as a compatibility thunk
1097
for older plugins that don't use e.g. the CommitBuilder
1101
return self._real_repository.start_write_group()
1103
def _unlock(self, token):
1104
path = self.bzrdir._path_for_remote_call(self._client)
1106
# with no token the remote repository is not persistently locked.
1108
err_context = {'token': token}
1109
response = self._call('Repository.unlock', path, token,
1111
if response == ('ok',):
1114
raise errors.UnexpectedSmartServerResponse(response)
1116
@only_raises(errors.LockNotHeld, errors.LockBroken)
1118
if not self._lock_count:
1119
return lock.cant_unlock_not_held(self)
1120
self._lock_count -= 1
1121
if self._lock_count > 0:
1123
self._unstacked_provider.disable_cache()
1124
old_mode = self._lock_mode
1125
self._lock_mode = None
1127
# The real repository is responsible at present for raising an
1128
# exception if it's in an unfinished write group. However, it
1129
# normally will *not* actually remove the lock from disk - that's
1130
# done by the server on receiving the Repository.unlock call.
1131
# This is just to let the _real_repository stay up to date.
1132
if self._real_repository is not None:
1133
self._real_repository.unlock()
1135
# The rpc-level lock should be released even if there was a
1136
# problem releasing the vfs-based lock.
1138
# Only write-locked repositories need to make a remote method
1139
# call to perform the unlock.
1140
old_token = self._lock_token
1141
self._lock_token = None
1142
if not self._leave_lock:
1143
self._unlock(old_token)
1144
# Fallbacks are always 'lock_read()' so we don't pay attention to
1146
for repo in self._fallback_repositories:
1149
def break_lock(self):
1150
# should hand off to the network
1152
return self._real_repository.break_lock()
1154
def _get_tarball(self, compression):
1155
"""Return a TemporaryFile containing a repository tarball.
1157
Returns None if the server does not support sending tarballs.
1160
path = self.bzrdir._path_for_remote_call(self._client)
1162
response, protocol = self._call_expecting_body(
1163
'Repository.tarball', path, compression)
1164
except errors.UnknownSmartMethod:
1165
protocol.cancel_read_body()
1167
if response[0] == 'ok':
1168
# Extract the tarball and return it
1169
t = tempfile.NamedTemporaryFile()
1170
# TODO: rpc layer should read directly into it...
1171
t.write(protocol.read_body_bytes())
1174
raise errors.UnexpectedSmartServerResponse(response)
1176
def sprout(self, to_bzrdir, revision_id=None):
1177
# TODO: Option to control what format is created?
1179
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1181
dest_repo.fetch(self, revision_id=revision_id)
1184
### These methods are just thin shims to the VFS object for now.
1186
def revision_tree(self, revision_id):
1188
return self._real_repository.revision_tree(revision_id)
1190
def get_serializer_format(self):
1192
return self._real_repository.get_serializer_format()
1194
def get_commit_builder(self, branch, parents, config, timestamp=None,
1195
timezone=None, committer=None, revprops=None,
1197
# FIXME: It ought to be possible to call this without immediately
1198
# triggering _ensure_real. For now it's the easiest thing to do.
1200
real_repo = self._real_repository
1201
builder = real_repo.get_commit_builder(branch, parents,
1202
config, timestamp=timestamp, timezone=timezone,
1203
committer=committer, revprops=revprops, revision_id=revision_id)
1206
def add_fallback_repository(self, repository):
1207
"""Add a repository to use for looking up data not held locally.
1209
:param repository: A repository.
1211
if not self._format.supports_external_lookups:
1212
raise errors.UnstackableRepositoryFormat(
1213
self._format.network_name(), self.base)
1214
# We need to accumulate additional repositories here, to pass them in
1217
if self.is_locked():
1218
# We will call fallback.unlock() when we transition to the unlocked
1219
# state, so always add a lock here. If a caller passes us a locked
1220
# repository, they are responsible for unlocking it later.
1221
repository.lock_read()
1222
self._fallback_repositories.append(repository)
1223
# If self._real_repository was parameterised already (e.g. because a
1224
# _real_branch had its get_stacked_on_url method called), then the
1225
# repository to be added may already be in the _real_repositories list.
1226
if self._real_repository is not None:
1227
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1228
self._real_repository._fallback_repositories]
1229
if repository.bzrdir.root_transport.base not in fallback_locations:
1230
self._real_repository.add_fallback_repository(repository)
1232
def add_inventory(self, revid, inv, parents):
1234
return self._real_repository.add_inventory(revid, inv, parents)
1236
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1237
parents, basis_inv=None, propagate_caches=False):
1239
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1240
delta, new_revision_id, parents, basis_inv=basis_inv,
1241
propagate_caches=propagate_caches)
1243
def add_revision(self, rev_id, rev, inv=None, config=None):
1245
return self._real_repository.add_revision(
1246
rev_id, rev, inv=inv, config=config)
1249
def get_inventory(self, revision_id):
1251
return self._real_repository.get_inventory(revision_id)
1253
def iter_inventories(self, revision_ids, ordering=None):
1255
return self._real_repository.iter_inventories(revision_ids, ordering)
1258
def get_revision(self, revision_id):
1260
return self._real_repository.get_revision(revision_id)
1262
def get_transaction(self):
1264
return self._real_repository.get_transaction()
1267
def clone(self, a_bzrdir, revision_id=None):
1269
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1271
def make_working_trees(self):
1272
"""See Repository.make_working_trees"""
1274
return self._real_repository.make_working_trees()
1276
def refresh_data(self):
1277
"""Re-read any data needed to to synchronise with disk.
1279
This method is intended to be called after another repository instance
1280
(such as one used by a smart server) has inserted data into the
1281
repository. It may not be called during a write group, but may be
1282
called at any other time.
1284
if self.is_in_write_group():
1285
raise errors.InternalBzrError(
1286
"May not refresh_data while in a write group.")
1287
if self._real_repository is not None:
1288
self._real_repository.refresh_data()
1290
def revision_ids_to_search_result(self, result_set):
1291
"""Convert a set of revision ids to a graph SearchResult."""
1292
result_parents = set()
1293
for parents in self.get_graph().get_parent_map(
1294
result_set).itervalues():
1295
result_parents.update(parents)
1296
included_keys = result_set.intersection(result_parents)
1297
start_keys = result_set.difference(included_keys)
1298
exclude_keys = result_parents.difference(result_set)
1299
result = graph.SearchResult(start_keys, exclude_keys,
1300
len(result_set), result_set)
1304
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1305
"""Return the revision ids that other has that this does not.
1307
These are returned in topological order.
1309
revision_id: only return revision ids included by revision_id.
1311
return repository.InterRepository.get(
1312
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1314
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1316
# No base implementation to use as RemoteRepository is not a subclass
1317
# of Repository; so this is a copy of Repository.fetch().
1318
if fetch_spec is not None and revision_id is not None:
1319
raise AssertionError(
1320
"fetch_spec and revision_id are mutually exclusive.")
1321
if self.is_in_write_group():
1322
raise errors.InternalBzrError(
1323
"May not fetch while in a write group.")
1324
# fast path same-url fetch operations
1325
if (self.has_same_location(source)
1326
and fetch_spec is None
1327
and self._has_same_fallbacks(source)):
1328
# check that last_revision is in 'from' and then return a
1330
if (revision_id is not None and
1331
not revision.is_null(revision_id)):
1332
self.get_revision(revision_id)
1334
# if there is no specific appropriate InterRepository, this will get
1335
# the InterRepository base class, which raises an
1336
# IncompatibleRepositories when asked to fetch.
1337
inter = repository.InterRepository.get(source, self)
1338
return inter.fetch(revision_id=revision_id, pb=pb,
1339
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1341
def create_bundle(self, target, base, fileobj, format=None):
1343
self._real_repository.create_bundle(target, base, fileobj, format)
1346
def get_ancestry(self, revision_id, topo_sorted=True):
1348
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1350
def fileids_altered_by_revision_ids(self, revision_ids):
1352
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1354
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1356
return self._real_repository._get_versioned_file_checker(
1357
revisions, revision_versions_cache)
1359
def iter_files_bytes(self, desired_files):
1360
"""See Repository.iter_file_bytes.
1363
return self._real_repository.iter_files_bytes(desired_files)
1365
def get_parent_map(self, revision_ids):
1366
"""See bzrlib.Graph.get_parent_map()."""
1367
return self._make_parents_provider().get_parent_map(revision_ids)
1369
def _get_parent_map_rpc(self, keys):
1370
"""Helper for get_parent_map that performs the RPC."""
1371
medium = self._client._medium
1372
if medium._is_remote_before((1, 2)):
1373
# We already found out that the server can't understand
1374
# Repository.get_parent_map requests, so just fetch the whole
1377
# Note that this reads the whole graph, when only some keys are
1378
# wanted. On this old server there's no way (?) to get them all
1379
# in one go, and the user probably will have seen a warning about
1380
# the server being old anyhow.
1381
rg = self._get_revision_graph(None)
1382
# There is an API discrepancy between get_parent_map and
1383
# get_revision_graph. Specifically, a "key:()" pair in
1384
# get_revision_graph just means a node has no parents. For
1385
# "get_parent_map" it means the node is a ghost. So fix up the
1386
# graph to correct this.
1387
# https://bugs.launchpad.net/bzr/+bug/214894
1388
# There is one other "bug" which is that ghosts in
1389
# get_revision_graph() are not returned at all. But we won't worry
1390
# about that for now.
1391
for node_id, parent_ids in rg.iteritems():
1392
if parent_ids == ():
1393
rg[node_id] = (NULL_REVISION,)
1394
rg[NULL_REVISION] = ()
1399
raise ValueError('get_parent_map(None) is not valid')
1400
if NULL_REVISION in keys:
1401
keys.discard(NULL_REVISION)
1402
found_parents = {NULL_REVISION:()}
1404
return found_parents
1407
# TODO(Needs analysis): We could assume that the keys being requested
1408
# from get_parent_map are in a breadth first search, so typically they
1409
# will all be depth N from some common parent, and we don't have to
1410
# have the server iterate from the root parent, but rather from the
1411
# keys we're searching; and just tell the server the keyspace we
1412
# already have; but this may be more traffic again.
1414
# Transform self._parents_map into a search request recipe.
1415
# TODO: Manage this incrementally to avoid covering the same path
1416
# repeatedly. (The server will have to on each request, but the less
1417
# work done the better).
1419
# Negative caching notes:
1420
# new server sends missing when a request including the revid
1421
# 'include-missing:' is present in the request.
1422
# missing keys are serialised as missing:X, and we then call
1423
# provider.note_missing(X) for-all X
1424
parents_map = self._unstacked_provider.get_cached_map()
1425
if parents_map is None:
1426
# Repository is not locked, so there's no cache.
1428
# start_set is all the keys in the cache
1429
start_set = set(parents_map)
1430
# result set is all the references to keys in the cache
1431
result_parents = set()
1432
for parents in parents_map.itervalues():
1433
result_parents.update(parents)
1434
stop_keys = result_parents.difference(start_set)
1435
# We don't need to send ghosts back to the server as a position to
1437
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1438
key_count = len(parents_map)
1439
if (NULL_REVISION in result_parents
1440
and NULL_REVISION in self._unstacked_provider.missing_keys):
1441
# If we pruned NULL_REVISION from the stop_keys because it's also
1442
# in our cache of "missing" keys we need to increment our key count
1443
# by 1, because the reconsitituted SearchResult on the server will
1444
# still consider NULL_REVISION to be an included key.
1446
included_keys = start_set.intersection(result_parents)
1447
start_set.difference_update(included_keys)
1448
recipe = ('manual', start_set, stop_keys, key_count)
1449
body = self._serialise_search_recipe(recipe)
1450
path = self.bzrdir._path_for_remote_call(self._client)
1452
if type(key) is not str:
1454
"key %r not a plain string" % (key,))
1455
verb = 'Repository.get_parent_map'
1456
args = (path, 'include-missing:') + tuple(keys)
1458
response = self._call_with_body_bytes_expecting_body(
1460
except errors.UnknownSmartMethod:
1461
# Server does not support this method, so get the whole graph.
1462
# Worse, we have to force a disconnection, because the server now
1463
# doesn't realise it has a body on the wire to consume, so the
1464
# only way to recover is to abandon the connection.
1466
'Server is too old for fast get_parent_map, reconnecting. '
1467
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1469
# To avoid having to disconnect repeatedly, we keep track of the
1470
# fact the server doesn't understand remote methods added in 1.2.
1471
medium._remember_remote_is_before((1, 2))
1472
# Recurse just once and we should use the fallback code.
1473
return self._get_parent_map_rpc(keys)
1474
response_tuple, response_handler = response
1475
if response_tuple[0] not in ['ok']:
1476
response_handler.cancel_read_body()
1477
raise errors.UnexpectedSmartServerResponse(response_tuple)
1478
if response_tuple[0] == 'ok':
1479
coded = bz2.decompress(response_handler.read_body_bytes())
1481
# no revisions found
1483
lines = coded.split('\n')
1486
d = tuple(line.split())
1488
revision_graph[d[0]] = d[1:]
1491
if d[0].startswith('missing:'):
1493
self._unstacked_provider.note_missing_key(revid)
1495
# no parents - so give the Graph result
1497
revision_graph[d[0]] = (NULL_REVISION,)
1498
return revision_graph
1501
def get_signature_text(self, revision_id):
1503
return self._real_repository.get_signature_text(revision_id)
1506
def _get_inventory_xml(self, revision_id):
1508
return self._real_repository._get_inventory_xml(revision_id)
1510
def reconcile(self, other=None, thorough=False):
1512
return self._real_repository.reconcile(other=other, thorough=thorough)
1514
def all_revision_ids(self):
1516
return self._real_repository.all_revision_ids()
1519
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1521
return self._real_repository.get_deltas_for_revisions(revisions,
1522
specific_fileids=specific_fileids)
1525
def get_revision_delta(self, revision_id, specific_fileids=None):
1527
return self._real_repository.get_revision_delta(revision_id,
1528
specific_fileids=specific_fileids)
1531
def revision_trees(self, revision_ids):
1533
return self._real_repository.revision_trees(revision_ids)
1536
def get_revision_reconcile(self, revision_id):
1538
return self._real_repository.get_revision_reconcile(revision_id)
1541
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1543
return self._real_repository.check(revision_ids=revision_ids,
1544
callback_refs=callback_refs, check_repo=check_repo)
1546
def copy_content_into(self, destination, revision_id=None):
1548
return self._real_repository.copy_content_into(
1549
destination, revision_id=revision_id)
1551
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1552
# get a tarball of the remote repository, and copy from that into the
1554
from bzrlib import osutils
1556
# TODO: Maybe a progress bar while streaming the tarball?
1557
note("Copying repository content as tarball...")
1558
tar_file = self._get_tarball('bz2')
1559
if tar_file is None:
1561
destination = to_bzrdir.create_repository()
1563
tar = tarfile.open('repository', fileobj=tar_file,
1565
tmpdir = osutils.mkdtemp()
1567
_extract_tar(tar, tmpdir)
1568
tmp_bzrdir = BzrDir.open(tmpdir)
1569
tmp_repo = tmp_bzrdir.open_repository()
1570
tmp_repo.copy_content_into(destination, revision_id)
1572
osutils.rmtree(tmpdir)
1576
# TODO: Suggestion from john: using external tar is much faster than
1577
# python's tarfile library, but it may not work on windows.
1580
def inventories(self):
1581
"""Decorate the real repository for now.
1583
In the long term a full blown network facility is needed to
1584
avoid creating a real repository object locally.
1587
return self._real_repository.inventories
1590
def pack(self, hint=None):
1591
"""Compress the data within the repository.
1593
This is not currently implemented within the smart server.
1596
return self._real_repository.pack(hint=hint)
1599
def revisions(self):
1600
"""Decorate the real repository for now.
1602
In the short term this should become a real object to intercept graph
1605
In the long term a full blown network facility is needed.
1608
return self._real_repository.revisions
1610
def set_make_working_trees(self, new_value):
1612
new_value_str = "True"
1614
new_value_str = "False"
1615
path = self.bzrdir._path_for_remote_call(self._client)
1617
response = self._call(
1618
'Repository.set_make_working_trees', path, new_value_str)
1619
except errors.UnknownSmartMethod:
1621
self._real_repository.set_make_working_trees(new_value)
1623
if response[0] != 'ok':
1624
raise errors.UnexpectedSmartServerResponse(response)
1627
def signatures(self):
1628
"""Decorate the real repository for now.
1630
In the long term a full blown network facility is needed to avoid
1631
creating a real repository object locally.
1634
return self._real_repository.signatures
1637
def sign_revision(self, revision_id, gpg_strategy):
1639
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1643
"""Decorate the real repository for now.
1645
In the long term a full blown network facility is needed to avoid
1646
creating a real repository object locally.
1649
return self._real_repository.texts
1652
def get_revisions(self, revision_ids):
1654
return self._real_repository.get_revisions(revision_ids)
1656
def supports_rich_root(self):
1657
return self._format.rich_root_data
1659
def iter_reverse_revision_history(self, revision_id):
1661
return self._real_repository.iter_reverse_revision_history(revision_id)
1664
def _serializer(self):
1665
return self._format._serializer
1667
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1669
return self._real_repository.store_revision_signature(
1670
gpg_strategy, plaintext, revision_id)
1672
def add_signature_text(self, revision_id, signature):
1674
return self._real_repository.add_signature_text(revision_id, signature)
1676
def has_signature_for_revision_id(self, revision_id):
1678
return self._real_repository.has_signature_for_revision_id(revision_id)
1680
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1682
return self._real_repository.item_keys_introduced_by(revision_ids,
1683
_files_pb=_files_pb)
1685
def revision_graph_can_have_wrong_parents(self):
1686
# The answer depends on the remote repo format.
1688
return self._real_repository.revision_graph_can_have_wrong_parents()
1690
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1692
return self._real_repository._find_inconsistent_revision_parents(
1695
def _check_for_inconsistent_revision_parents(self):
1697
return self._real_repository._check_for_inconsistent_revision_parents()
1699
def _make_parents_provider(self, other=None):
1700
providers = [self._unstacked_provider]
1701
if other is not None:
1702
providers.insert(0, other)
1703
providers.extend(r._make_parents_provider() for r in
1704
self._fallback_repositories)
1705
return graph.StackedParentsProvider(providers)
1707
def _serialise_search_recipe(self, recipe):
1708
"""Serialise a graph search recipe.
1710
:param recipe: A search recipe (start, stop, count).
1711
:return: Serialised bytes.
1713
start_keys = ' '.join(recipe[1])
1714
stop_keys = ' '.join(recipe[2])
1715
count = str(recipe[3])
1716
return '\n'.join((start_keys, stop_keys, count))
1718
def _serialise_search_result(self, search_result):
1719
if isinstance(search_result, graph.PendingAncestryResult):
1720
parts = ['ancestry-of']
1721
parts.extend(search_result.heads)
1723
recipe = search_result.get_recipe()
1724
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1725
return '\n'.join(parts)
1728
path = self.bzrdir._path_for_remote_call(self._client)
1730
response = self._call('PackRepository.autopack', path)
1731
except errors.UnknownSmartMethod:
1733
self._real_repository._pack_collection.autopack()
1736
if response[0] != 'ok':
1737
raise errors.UnexpectedSmartServerResponse(response)
1740
class RemoteStreamSink(repository.StreamSink):
1742
def _insert_real(self, stream, src_format, resume_tokens):
1743
self.target_repo._ensure_real()
1744
sink = self.target_repo._real_repository._get_sink()
1745
result = sink.insert_stream(stream, src_format, resume_tokens)
1747
self.target_repo.autopack()
1750
def insert_stream(self, stream, src_format, resume_tokens):
1751
target = self.target_repo
1752
target._unstacked_provider.missing_keys.clear()
1753
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1754
if target._lock_token:
1755
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1756
lock_args = (target._lock_token or '',)
1758
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1760
client = target._client
1761
medium = client._medium
1762
path = target.bzrdir._path_for_remote_call(client)
1763
# Probe for the verb to use with an empty stream before sending the
1764
# real stream to it. We do this both to avoid the risk of sending a
1765
# large request that is then rejected, and because we don't want to
1766
# implement a way to buffer, rewind, or restart the stream.
1768
for verb, required_version in candidate_calls:
1769
if medium._is_remote_before(required_version):
1772
# We've already done the probing (and set _is_remote_before) on
1773
# a previous insert.
1776
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1778
response = client.call_with_body_stream(
1779
(verb, path, '') + lock_args, byte_stream)
1780
except errors.UnknownSmartMethod:
1781
medium._remember_remote_is_before(required_version)
1787
return self._insert_real(stream, src_format, resume_tokens)
1788
self._last_inv_record = None
1789
self._last_substream = None
1790
if required_version < (1, 19):
1791
# Remote side doesn't support inventory deltas. Wrap the stream to
1792
# make sure we don't send any. If the stream contains inventory
1793
# deltas we'll interrupt the smart insert_stream request and
1795
stream = self._stop_stream_if_inventory_delta(stream)
1796
byte_stream = smart_repo._stream_to_byte_stream(
1798
resume_tokens = ' '.join(resume_tokens)
1799
response = client.call_with_body_stream(
1800
(verb, path, resume_tokens) + lock_args, byte_stream)
1801
if response[0][0] not in ('ok', 'missing-basis'):
1802
raise errors.UnexpectedSmartServerResponse(response)
1803
if self._last_substream is not None:
1804
# The stream included an inventory-delta record, but the remote
1805
# side isn't new enough to support them. So we need to send the
1806
# rest of the stream via VFS.
1807
self.target_repo.refresh_data()
1808
return self._resume_stream_with_vfs(response, src_format)
1809
if response[0][0] == 'missing-basis':
1810
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1811
resume_tokens = tokens
1812
return resume_tokens, set(missing_keys)
1814
self.target_repo.refresh_data()
1817
def _resume_stream_with_vfs(self, response, src_format):
1818
"""Resume sending a stream via VFS, first resending the record and
1819
substream that couldn't be sent via an insert_stream verb.
1821
if response[0][0] == 'missing-basis':
1822
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1823
# Ignore missing_keys, we haven't finished inserting yet
1826
def resume_substream():
1827
# Yield the substream that was interrupted.
1828
for record in self._last_substream:
1830
self._last_substream = None
1831
def resume_stream():
1832
# Finish sending the interrupted substream
1833
yield ('inventory-deltas', resume_substream())
1834
# Then simply continue sending the rest of the stream.
1835
for substream_kind, substream in self._last_stream:
1836
yield substream_kind, substream
1837
return self._insert_real(resume_stream(), src_format, tokens)
1839
def _stop_stream_if_inventory_delta(self, stream):
1840
"""Normally this just lets the original stream pass-through unchanged.
1842
However if any 'inventory-deltas' substream occurs it will stop
1843
streaming, and store the interrupted substream and stream in
1844
self._last_substream and self._last_stream so that the stream can be
1845
resumed by _resume_stream_with_vfs.
1848
stream_iter = iter(stream)
1849
for substream_kind, substream in stream_iter:
1850
if substream_kind == 'inventory-deltas':
1851
self._last_substream = substream
1852
self._last_stream = stream_iter
1855
yield substream_kind, substream
1858
class RemoteStreamSource(repository.StreamSource):
1859
"""Stream data from a remote server."""
1861
def get_stream(self, search):
1862
if (self.from_repository._fallback_repositories and
1863
self.to_format._fetch_order == 'topological'):
1864
return self._real_stream(self.from_repository, search)
1867
repos = [self.from_repository]
1873
repos.extend(repo._fallback_repositories)
1874
sources.append(repo)
1875
return self.missing_parents_chain(search, sources)
1877
def get_stream_for_missing_keys(self, missing_keys):
1878
self.from_repository._ensure_real()
1879
real_repo = self.from_repository._real_repository
1880
real_source = real_repo._get_source(self.to_format)
1881
return real_source.get_stream_for_missing_keys(missing_keys)
1883
def _real_stream(self, repo, search):
1884
"""Get a stream for search from repo.
1886
This never called RemoteStreamSource.get_stream, and is a heler
1887
for RemoteStreamSource._get_stream to allow getting a stream
1888
reliably whether fallback back because of old servers or trying
1889
to stream from a non-RemoteRepository (which the stacked support
1892
source = repo._get_source(self.to_format)
1893
if isinstance(source, RemoteStreamSource):
1895
source = repo._real_repository._get_source(self.to_format)
1896
return source.get_stream(search)
1898
def _get_stream(self, repo, search):
1899
"""Core worker to get a stream from repo for search.
1901
This is used by both get_stream and the stacking support logic. It
1902
deliberately gets a stream for repo which does not need to be
1903
self.from_repository. In the event that repo is not Remote, or
1904
cannot do a smart stream, a fallback is made to the generic
1905
repository._get_stream() interface, via self._real_stream.
1907
In the event of stacking, streams from _get_stream will not
1908
contain all the data for search - this is normal (see get_stream).
1910
:param repo: A repository.
1911
:param search: A search.
1913
# Fallbacks may be non-smart
1914
if not isinstance(repo, RemoteRepository):
1915
return self._real_stream(repo, search)
1916
client = repo._client
1917
medium = client._medium
1918
path = repo.bzrdir._path_for_remote_call(client)
1919
search_bytes = repo._serialise_search_result(search)
1920
args = (path, self.to_format.network_name())
1922
('Repository.get_stream_1.19', (1, 19)),
1923
('Repository.get_stream', (1, 13))]
1925
for verb, version in candidate_verbs:
1926
if medium._is_remote_before(version):
1929
response = repo._call_with_body_bytes_expecting_body(
1930
verb, args, search_bytes)
1931
except errors.UnknownSmartMethod:
1932
medium._remember_remote_is_before(version)
1934
response_tuple, response_handler = response
1938
return self._real_stream(repo, search)
1939
if response_tuple[0] != 'ok':
1940
raise errors.UnexpectedSmartServerResponse(response_tuple)
1941
byte_stream = response_handler.read_streamed_body()
1942
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1943
if src_format.network_name() != repo._format.network_name():
1944
raise AssertionError(
1945
"Mismatched RemoteRepository and stream src %r, %r" % (
1946
src_format.network_name(), repo._format.network_name()))
1949
def missing_parents_chain(self, search, sources):
1950
"""Chain multiple streams together to handle stacking.
1952
:param search: The overall search to satisfy with streams.
1953
:param sources: A list of Repository objects to query.
1955
self.from_serialiser = self.from_repository._format._serializer
1956
self.seen_revs = set()
1957
self.referenced_revs = set()
1958
# If there are heads in the search, or the key count is > 0, we are not
1960
while not search.is_empty() and len(sources) > 1:
1961
source = sources.pop(0)
1962
stream = self._get_stream(source, search)
1963
for kind, substream in stream:
1964
if kind != 'revisions':
1965
yield kind, substream
1967
yield kind, self.missing_parents_rev_handler(substream)
1968
search = search.refine(self.seen_revs, self.referenced_revs)
1969
self.seen_revs = set()
1970
self.referenced_revs = set()
1971
if not search.is_empty():
1972
for kind, stream in self._get_stream(sources[0], search):
1975
def missing_parents_rev_handler(self, substream):
1976
for content in substream:
1977
revision_bytes = content.get_bytes_as('fulltext')
1978
revision = self.from_serialiser.read_revision_from_string(
1980
self.seen_revs.add(content.key[-1])
1981
self.referenced_revs.update(revision.parent_ids)
1985
class RemoteBranchLockableFiles(LockableFiles):
1986
"""A 'LockableFiles' implementation that talks to a smart server.
1988
This is not a public interface class.
1991
def __init__(self, bzrdir, _client):
1992
self.bzrdir = bzrdir
1993
self._client = _client
1994
self._need_find_modes = True
1995
LockableFiles.__init__(
1996
self, bzrdir.get_branch_transport(None),
1997
'lock', lockdir.LockDir)
1999
def _find_modes(self):
2000
# RemoteBranches don't let the client set the mode of control files.
2001
self._dir_mode = None
2002
self._file_mode = None
2005
class RemoteBranchFormat(branch.BranchFormat):
2007
def __init__(self, network_name=None):
2008
super(RemoteBranchFormat, self).__init__()
2009
self._matchingbzrdir = RemoteBzrDirFormat()
2010
self._matchingbzrdir.set_branch_format(self)
2011
self._custom_format = None
2012
self._network_name = network_name
2014
def __eq__(self, other):
2015
return (isinstance(other, RemoteBranchFormat) and
2016
self.__dict__ == other.__dict__)
2018
def _ensure_real(self):
2019
if self._custom_format is None:
2020
self._custom_format = branch.network_format_registry.get(
2023
def get_format_description(self):
2025
return 'Remote: ' + self._custom_format.get_format_description()
2027
def network_name(self):
2028
return self._network_name
2030
def open(self, a_bzrdir, ignore_fallbacks=False):
2031
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2033
def _vfs_initialize(self, a_bzrdir):
2034
# Initialisation when using a local bzrdir object, or a non-vfs init
2035
# method is not available on the server.
2036
# self._custom_format is always set - the start of initialize ensures
2038
if isinstance(a_bzrdir, RemoteBzrDir):
2039
a_bzrdir._ensure_real()
2040
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2042
# We assume the bzrdir is parameterised; it may not be.
2043
result = self._custom_format.initialize(a_bzrdir)
2044
if (isinstance(a_bzrdir, RemoteBzrDir) and
2045
not isinstance(result, RemoteBranch)):
2046
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2049
def initialize(self, a_bzrdir):
2050
# 1) get the network name to use.
2051
if self._custom_format:
2052
network_name = self._custom_format.network_name()
2054
# Select the current bzrlib default and ask for that.
2055
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2056
reference_format = reference_bzrdir_format.get_branch_format()
2057
self._custom_format = reference_format
2058
network_name = reference_format.network_name()
2059
# Being asked to create on a non RemoteBzrDir:
2060
if not isinstance(a_bzrdir, RemoteBzrDir):
2061
return self._vfs_initialize(a_bzrdir)
2062
medium = a_bzrdir._client._medium
2063
if medium._is_remote_before((1, 13)):
2064
return self._vfs_initialize(a_bzrdir)
2065
# Creating on a remote bzr dir.
2066
# 2) try direct creation via RPC
2067
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2068
verb = 'BzrDir.create_branch'
2070
response = a_bzrdir._call(verb, path, network_name)
2071
except errors.UnknownSmartMethod:
2072
# Fallback - use vfs methods
2073
medium._remember_remote_is_before((1, 13))
2074
return self._vfs_initialize(a_bzrdir)
2075
if response[0] != 'ok':
2076
raise errors.UnexpectedSmartServerResponse(response)
2077
# Turn the response into a RemoteRepository object.
2078
format = RemoteBranchFormat(network_name=response[1])
2079
repo_format = response_tuple_to_repo_format(response[3:])
2080
if response[2] == '':
2081
repo_bzrdir = a_bzrdir
2083
repo_bzrdir = RemoteBzrDir(
2084
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2086
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2087
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2088
format=format, setup_stacking=False)
2089
# XXX: We know this is a new branch, so it must have revno 0, revid
2090
# NULL_REVISION. Creating the branch locked would make this be unable
2091
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2092
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2093
return remote_branch
2095
def make_tags(self, branch):
2097
return self._custom_format.make_tags(branch)
2099
def supports_tags(self):
2100
# Remote branches might support tags, but we won't know until we
2101
# access the real remote branch.
2103
return self._custom_format.supports_tags()
2105
def supports_stacking(self):
2107
return self._custom_format.supports_stacking()
2109
def supports_set_append_revisions_only(self):
2111
return self._custom_format.supports_set_append_revisions_only()
2114
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2115
"""Branch stored on a server accessed by HPSS RPC.
2117
At the moment most operations are mapped down to simple file operations.
2120
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2121
_client=None, format=None, setup_stacking=True):
2122
"""Create a RemoteBranch instance.
2124
:param real_branch: An optional local implementation of the branch
2125
format, usually accessing the data via the VFS.
2126
:param _client: Private parameter for testing.
2127
:param format: A RemoteBranchFormat object, None to create one
2128
automatically. If supplied it should have a network_name already
2130
:param setup_stacking: If True make an RPC call to determine the
2131
stacked (or not) status of the branch. If False assume the branch
2134
# We intentionally don't call the parent class's __init__, because it
2135
# will try to assign to self.tags, which is a property in this subclass.
2136
# And the parent's __init__ doesn't do much anyway.
2137
self.bzrdir = remote_bzrdir
2138
if _client is not None:
2139
self._client = _client
2141
self._client = remote_bzrdir._client
2142
self.repository = remote_repository
2143
if real_branch is not None:
2144
self._real_branch = real_branch
2145
# Give the remote repository the matching real repo.
2146
real_repo = self._real_branch.repository
2147
if isinstance(real_repo, RemoteRepository):
2148
real_repo._ensure_real()
2149
real_repo = real_repo._real_repository
2150
self.repository._set_real_repository(real_repo)
2151
# Give the branch the remote repository to let fast-pathing happen.
2152
self._real_branch.repository = self.repository
2154
self._real_branch = None
2155
# Fill out expected attributes of branch for bzrlib API users.
2156
self._clear_cached_state()
2157
self.base = self.bzrdir.root_transport.base
2158
self._control_files = None
2159
self._lock_mode = None
2160
self._lock_token = None
2161
self._repo_lock_token = None
2162
self._lock_count = 0
2163
self._leave_lock = False
2164
# Setup a format: note that we cannot call _ensure_real until all the
2165
# attributes above are set: This code cannot be moved higher up in this
2168
self._format = RemoteBranchFormat()
2169
if real_branch is not None:
2170
self._format._network_name = \
2171
self._real_branch._format.network_name()
2173
self._format = format
2174
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2175
# branch.open_branch method.
2176
self._real_ignore_fallbacks = not setup_stacking
2177
if not self._format._network_name:
2178
# Did not get from open_branchV2 - old server.
2180
self._format._network_name = \
2181
self._real_branch._format.network_name()
2182
self.tags = self._format.make_tags(self)
2183
# The base class init is not called, so we duplicate this:
2184
hooks = branch.Branch.hooks['open']
2187
self._is_stacked = False
2189
self._setup_stacking()
2191
def _setup_stacking(self):
2192
# configure stacking into the remote repository, by reading it from
2195
fallback_url = self.get_stacked_on_url()
2196
except (errors.NotStacked, errors.UnstackableBranchFormat,
2197
errors.UnstackableRepositoryFormat), e:
2199
self._is_stacked = True
2200
self._activate_fallback_location(fallback_url)
2202
def _get_config(self):
2203
return RemoteBranchConfig(self)
2205
def _get_real_transport(self):
2206
# if we try vfs access, return the real branch's vfs transport
2208
return self._real_branch._transport
2210
_transport = property(_get_real_transport)
2213
return "%s(%s)" % (self.__class__.__name__, self.base)
2217
def _ensure_real(self):
2218
"""Ensure that there is a _real_branch set.
2220
Used before calls to self._real_branch.
2222
if self._real_branch is None:
2223
if not vfs.vfs_enabled():
2224
raise AssertionError('smart server vfs must be enabled '
2225
'to use vfs implementation')
2226
self.bzrdir._ensure_real()
2227
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2228
ignore_fallbacks=self._real_ignore_fallbacks)
2229
if self.repository._real_repository is None:
2230
# Give the remote repository the matching real repo.
2231
real_repo = self._real_branch.repository
2232
if isinstance(real_repo, RemoteRepository):
2233
real_repo._ensure_real()
2234
real_repo = real_repo._real_repository
2235
self.repository._set_real_repository(real_repo)
2236
# Give the real branch the remote repository to let fast-pathing
2238
self._real_branch.repository = self.repository
2239
if self._lock_mode == 'r':
2240
self._real_branch.lock_read()
2241
elif self._lock_mode == 'w':
2242
self._real_branch.lock_write(token=self._lock_token)
2244
def _translate_error(self, err, **context):
2245
self.repository._translate_error(err, branch=self, **context)
2247
def _clear_cached_state(self):
2248
super(RemoteBranch, self)._clear_cached_state()
2249
if self._real_branch is not None:
2250
self._real_branch._clear_cached_state()
2252
def _clear_cached_state_of_remote_branch_only(self):
2253
"""Like _clear_cached_state, but doesn't clear the cache of
2256
This is useful when falling back to calling a method of
2257
self._real_branch that changes state. In that case the underlying
2258
branch changes, so we need to invalidate this RemoteBranch's cache of
2259
it. However, there's no need to invalidate the _real_branch's cache
2260
too, in fact doing so might harm performance.
2262
super(RemoteBranch, self)._clear_cached_state()
2265
def control_files(self):
2266
# Defer actually creating RemoteBranchLockableFiles until its needed,
2267
# because it triggers an _ensure_real that we otherwise might not need.
2268
if self._control_files is None:
2269
self._control_files = RemoteBranchLockableFiles(
2270
self.bzrdir, self._client)
2271
return self._control_files
2273
def _get_checkout_format(self):
2275
return self._real_branch._get_checkout_format()
2277
def get_physical_lock_status(self):
2278
"""See Branch.get_physical_lock_status()."""
2279
# should be an API call to the server, as branches must be lockable.
2281
return self._real_branch.get_physical_lock_status()
2283
def get_stacked_on_url(self):
2284
"""Get the URL this branch is stacked against.
2286
:raises NotStacked: If the branch is not stacked.
2287
:raises UnstackableBranchFormat: If the branch does not support
2289
:raises UnstackableRepositoryFormat: If the repository does not support
2293
# there may not be a repository yet, so we can't use
2294
# self._translate_error, so we can't use self._call either.
2295
response = self._client.call('Branch.get_stacked_on_url',
2296
self._remote_path())
2297
except errors.ErrorFromSmartServer, err:
2298
# there may not be a repository yet, so we can't call through
2299
# its _translate_error
2300
_translate_error(err, branch=self)
2301
except errors.UnknownSmartMethod, err:
2303
return self._real_branch.get_stacked_on_url()
2304
if response[0] != 'ok':
2305
raise errors.UnexpectedSmartServerResponse(response)
2308
def set_stacked_on_url(self, url):
2309
branch.Branch.set_stacked_on_url(self, url)
2311
self._is_stacked = False
2313
self._is_stacked = True
2315
def _vfs_get_tags_bytes(self):
2317
return self._real_branch._get_tags_bytes()
2319
def _get_tags_bytes(self):
2320
medium = self._client._medium
2321
if medium._is_remote_before((1, 13)):
2322
return self._vfs_get_tags_bytes()
2324
response = self._call('Branch.get_tags_bytes', self._remote_path())
2325
except errors.UnknownSmartMethod:
2326
medium._remember_remote_is_before((1, 13))
2327
return self._vfs_get_tags_bytes()
2330
def _vfs_set_tags_bytes(self, bytes):
2332
return self._real_branch._set_tags_bytes(bytes)
2334
def _set_tags_bytes(self, bytes):
2335
medium = self._client._medium
2336
if medium._is_remote_before((1, 18)):
2337
self._vfs_set_tags_bytes(bytes)
2341
self._remote_path(), self._lock_token, self._repo_lock_token)
2342
response = self._call_with_body_bytes(
2343
'Branch.set_tags_bytes', args, bytes)
2344
except errors.UnknownSmartMethod:
2345
medium._remember_remote_is_before((1, 18))
2346
self._vfs_set_tags_bytes(bytes)
2348
def lock_read(self):
2349
self.repository.lock_read()
2350
if not self._lock_mode:
2351
self._note_lock('r')
2352
self._lock_mode = 'r'
2353
self._lock_count = 1
2354
if self._real_branch is not None:
2355
self._real_branch.lock_read()
2357
self._lock_count += 1
2359
def _remote_lock_write(self, token):
2361
branch_token = repo_token = ''
2363
branch_token = token
2364
repo_token = self.repository.lock_write()
2365
self.repository.unlock()
2366
err_context = {'token': token}
2367
response = self._call(
2368
'Branch.lock_write', self._remote_path(), branch_token,
2369
repo_token or '', **err_context)
2370
if response[0] != 'ok':
2371
raise errors.UnexpectedSmartServerResponse(response)
2372
ok, branch_token, repo_token = response
2373
return branch_token, repo_token
2375
def lock_write(self, token=None):
2376
if not self._lock_mode:
2377
self._note_lock('w')
2378
# Lock the branch and repo in one remote call.
2379
remote_tokens = self._remote_lock_write(token)
2380
self._lock_token, self._repo_lock_token = remote_tokens
2381
if not self._lock_token:
2382
raise SmartProtocolError('Remote server did not return a token!')
2383
# Tell the self.repository object that it is locked.
2384
self.repository.lock_write(
2385
self._repo_lock_token, _skip_rpc=True)
2387
if self._real_branch is not None:
2388
self._real_branch.lock_write(token=self._lock_token)
2389
if token is not None:
2390
self._leave_lock = True
2392
self._leave_lock = False
2393
self._lock_mode = 'w'
2394
self._lock_count = 1
2395
elif self._lock_mode == 'r':
2396
raise errors.ReadOnlyTransaction
2398
if token is not None:
2399
# A token was given to lock_write, and we're relocking, so
2400
# check that the given token actually matches the one we
2402
if token != self._lock_token:
2403
raise errors.TokenMismatch(token, self._lock_token)
2404
self._lock_count += 1
2405
# Re-lock the repository too.
2406
self.repository.lock_write(self._repo_lock_token)
2407
return self._lock_token or None
2409
def _unlock(self, branch_token, repo_token):
2410
err_context = {'token': str((branch_token, repo_token))}
2411
response = self._call(
2412
'Branch.unlock', self._remote_path(), branch_token,
2413
repo_token or '', **err_context)
2414
if response == ('ok',):
2416
raise errors.UnexpectedSmartServerResponse(response)
2418
@only_raises(errors.LockNotHeld, errors.LockBroken)
2421
self._lock_count -= 1
2422
if not self._lock_count:
2423
self._clear_cached_state()
2424
mode = self._lock_mode
2425
self._lock_mode = None
2426
if self._real_branch is not None:
2427
if (not self._leave_lock and mode == 'w' and
2428
self._repo_lock_token):
2429
# If this RemoteBranch will remove the physical lock
2430
# for the repository, make sure the _real_branch
2431
# doesn't do it first. (Because the _real_branch's
2432
# repository is set to be the RemoteRepository.)
2433
self._real_branch.repository.leave_lock_in_place()
2434
self._real_branch.unlock()
2436
# Only write-locked branched need to make a remote method
2437
# call to perform the unlock.
2439
if not self._lock_token:
2440
raise AssertionError('Locked, but no token!')
2441
branch_token = self._lock_token
2442
repo_token = self._repo_lock_token
2443
self._lock_token = None
2444
self._repo_lock_token = None
2445
if not self._leave_lock:
2446
self._unlock(branch_token, repo_token)
2448
self.repository.unlock()
2450
def break_lock(self):
2452
return self._real_branch.break_lock()
2454
def leave_lock_in_place(self):
2455
if not self._lock_token:
2456
raise NotImplementedError(self.leave_lock_in_place)
2457
self._leave_lock = True
2459
def dont_leave_lock_in_place(self):
2460
if not self._lock_token:
2461
raise NotImplementedError(self.dont_leave_lock_in_place)
2462
self._leave_lock = False
2465
def get_rev_id(self, revno, history=None):
2467
return _mod_revision.NULL_REVISION
2468
last_revision_info = self.last_revision_info()
2469
ok, result = self.repository.get_rev_id_for_revno(
2470
revno, last_revision_info)
2473
missing_parent = result[1]
2474
# Either the revision named by the server is missing, or its parent
2475
# is. Call get_parent_map to determine which, so that we report a
2477
parent_map = self.repository.get_parent_map([missing_parent])
2478
if missing_parent in parent_map:
2479
missing_parent = parent_map[missing_parent]
2480
raise errors.RevisionNotPresent(missing_parent, self.repository)
2482
def _last_revision_info(self):
2483
response = self._call('Branch.last_revision_info', self._remote_path())
2484
if response[0] != 'ok':
2485
raise SmartProtocolError('unexpected response code %s' % (response,))
2486
revno = int(response[1])
2487
last_revision = response[2]
2488
return (revno, last_revision)
2490
def _gen_revision_history(self):
2491
"""See Branch._gen_revision_history()."""
2492
if self._is_stacked:
2494
return self._real_branch._gen_revision_history()
2495
response_tuple, response_handler = self._call_expecting_body(
2496
'Branch.revision_history', self._remote_path())
2497
if response_tuple[0] != 'ok':
2498
raise errors.UnexpectedSmartServerResponse(response_tuple)
2499
result = response_handler.read_body_bytes().split('\x00')
2504
def _remote_path(self):
2505
return self.bzrdir._path_for_remote_call(self._client)
2507
def _set_last_revision_descendant(self, revision_id, other_branch,
2508
allow_diverged=False, allow_overwrite_descendant=False):
2509
# This performs additional work to meet the hook contract; while its
2510
# undesirable, we have to synthesise the revno to call the hook, and
2511
# not calling the hook is worse as it means changes can't be prevented.
2512
# Having calculated this though, we can't just call into
2513
# set_last_revision_info as a simple call, because there is a set_rh
2514
# hook that some folk may still be using.
2515
old_revno, old_revid = self.last_revision_info()
2516
history = self._lefthand_history(revision_id)
2517
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2518
err_context = {'other_branch': other_branch}
2519
response = self._call('Branch.set_last_revision_ex',
2520
self._remote_path(), self._lock_token, self._repo_lock_token,
2521
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2523
self._clear_cached_state()
2524
if len(response) != 3 and response[0] != 'ok':
2525
raise errors.UnexpectedSmartServerResponse(response)
2526
new_revno, new_revision_id = response[1:]
2527
self._last_revision_info_cache = new_revno, new_revision_id
2528
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2529
if self._real_branch is not None:
2530
cache = new_revno, new_revision_id
2531
self._real_branch._last_revision_info_cache = cache
2533
def _set_last_revision(self, revision_id):
2534
old_revno, old_revid = self.last_revision_info()
2535
# This performs additional work to meet the hook contract; while its
2536
# undesirable, we have to synthesise the revno to call the hook, and
2537
# not calling the hook is worse as it means changes can't be prevented.
2538
# Having calculated this though, we can't just call into
2539
# set_last_revision_info as a simple call, because there is a set_rh
2540
# hook that some folk may still be using.
2541
history = self._lefthand_history(revision_id)
2542
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2543
self._clear_cached_state()
2544
response = self._call('Branch.set_last_revision',
2545
self._remote_path(), self._lock_token, self._repo_lock_token,
2547
if response != ('ok',):
2548
raise errors.UnexpectedSmartServerResponse(response)
2549
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2552
def set_revision_history(self, rev_history):
2553
# Send just the tip revision of the history; the server will generate
2554
# the full history from that. If the revision doesn't exist in this
2555
# branch, NoSuchRevision will be raised.
2556
if rev_history == []:
2559
rev_id = rev_history[-1]
2560
self._set_last_revision(rev_id)
2561
for hook in branch.Branch.hooks['set_rh']:
2562
hook(self, rev_history)
2563
self._cache_revision_history(rev_history)
2565
def _get_parent_location(self):
2566
medium = self._client._medium
2567
if medium._is_remote_before((1, 13)):
2568
return self._vfs_get_parent_location()
2570
response = self._call('Branch.get_parent', self._remote_path())
2571
except errors.UnknownSmartMethod:
2572
medium._remember_remote_is_before((1, 13))
2573
return self._vfs_get_parent_location()
2574
if len(response) != 1:
2575
raise errors.UnexpectedSmartServerResponse(response)
2576
parent_location = response[0]
2577
if parent_location == '':
2579
return parent_location
2581
def _vfs_get_parent_location(self):
2583
return self._real_branch._get_parent_location()
2585
def _set_parent_location(self, url):
2586
medium = self._client._medium
2587
if medium._is_remote_before((1, 15)):
2588
return self._vfs_set_parent_location(url)
2590
call_url = url or ''
2591
if type(call_url) is not str:
2592
raise AssertionError('url must be a str or None (%s)' % url)
2593
response = self._call('Branch.set_parent_location',
2594
self._remote_path(), self._lock_token, self._repo_lock_token,
2596
except errors.UnknownSmartMethod:
2597
medium._remember_remote_is_before((1, 15))
2598
return self._vfs_set_parent_location(url)
2600
raise errors.UnexpectedSmartServerResponse(response)
2602
def _vfs_set_parent_location(self, url):
2604
return self._real_branch._set_parent_location(url)
2607
def pull(self, source, overwrite=False, stop_revision=None,
2609
self._clear_cached_state_of_remote_branch_only()
2611
return self._real_branch.pull(
2612
source, overwrite=overwrite, stop_revision=stop_revision,
2613
_override_hook_target=self, **kwargs)
2616
def push(self, target, overwrite=False, stop_revision=None):
2618
return self._real_branch.push(
2619
target, overwrite=overwrite, stop_revision=stop_revision,
2620
_override_hook_source_branch=self)
2622
def is_locked(self):
2623
return self._lock_count >= 1
2626
def revision_id_to_revno(self, revision_id):
2628
return self._real_branch.revision_id_to_revno(revision_id)
2631
def set_last_revision_info(self, revno, revision_id):
2632
# XXX: These should be returned by the set_last_revision_info verb
2633
old_revno, old_revid = self.last_revision_info()
2634
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2635
revision_id = ensure_null(revision_id)
2637
response = self._call('Branch.set_last_revision_info',
2638
self._remote_path(), self._lock_token, self._repo_lock_token,
2639
str(revno), revision_id)
2640
except errors.UnknownSmartMethod:
2642
self._clear_cached_state_of_remote_branch_only()
2643
self._real_branch.set_last_revision_info(revno, revision_id)
2644
self._last_revision_info_cache = revno, revision_id
2646
if response == ('ok',):
2647
self._clear_cached_state()
2648
self._last_revision_info_cache = revno, revision_id
2649
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2650
# Update the _real_branch's cache too.
2651
if self._real_branch is not None:
2652
cache = self._last_revision_info_cache
2653
self._real_branch._last_revision_info_cache = cache
2655
raise errors.UnexpectedSmartServerResponse(response)
2658
def generate_revision_history(self, revision_id, last_rev=None,
2660
medium = self._client._medium
2661
if not medium._is_remote_before((1, 6)):
2662
# Use a smart method for 1.6 and above servers
2664
self._set_last_revision_descendant(revision_id, other_branch,
2665
allow_diverged=True, allow_overwrite_descendant=True)
2667
except errors.UnknownSmartMethod:
2668
medium._remember_remote_is_before((1, 6))
2669
self._clear_cached_state_of_remote_branch_only()
2670
self.set_revision_history(self._lefthand_history(revision_id,
2671
last_rev=last_rev,other_branch=other_branch))
2673
def set_push_location(self, location):
2675
return self._real_branch.set_push_location(location)
2678
class RemoteConfig(object):
2679
"""A Config that reads and writes from smart verbs.
2681
It is a low-level object that considers config data to be name/value pairs
2682
that may be associated with a section. Assigning meaning to the these
2683
values is done at higher levels like bzrlib.config.TreeConfig.
2686
def get_option(self, name, section=None, default=None):
2687
"""Return the value associated with a named option.
2689
:param name: The name of the value
2690
:param section: The section the option is in (if any)
2691
:param default: The value to return if the value is not set
2692
:return: The value or default value
2695
configobj = self._get_configobj()
2697
section_obj = configobj
2700
section_obj = configobj[section]
2703
return section_obj.get(name, default)
2704
except errors.UnknownSmartMethod:
2705
return self._vfs_get_option(name, section, default)
2707
def _response_to_configobj(self, response):
2708
if len(response[0]) and response[0][0] != 'ok':
2709
raise errors.UnexpectedSmartServerResponse(response)
2710
lines = response[1].read_body_bytes().splitlines()
2711
return config.ConfigObj(lines, encoding='utf-8')
2714
class RemoteBranchConfig(RemoteConfig):
2715
"""A RemoteConfig for Branches."""
2717
def __init__(self, branch):
2718
self._branch = branch
2720
def _get_configobj(self):
2721
path = self._branch._remote_path()
2722
response = self._branch._client.call_expecting_body(
2723
'Branch.get_config_file', path)
2724
return self._response_to_configobj(response)
2726
def set_option(self, value, name, section=None):
2727
"""Set the value associated with a named option.
2729
:param value: The value to set
2730
:param name: The name of the value to set
2731
:param section: The section the option is in (if any)
2733
medium = self._branch._client._medium
2734
if medium._is_remote_before((1, 14)):
2735
return self._vfs_set_option(value, name, section)
2737
path = self._branch._remote_path()
2738
response = self._branch._client.call('Branch.set_config_option',
2739
path, self._branch._lock_token, self._branch._repo_lock_token,
2740
value.encode('utf8'), name, section or '')
2741
except errors.UnknownSmartMethod:
2742
medium._remember_remote_is_before((1, 14))
2743
return self._vfs_set_option(value, name, section)
2745
raise errors.UnexpectedSmartServerResponse(response)
2747
def _real_object(self):
2748
self._branch._ensure_real()
2749
return self._branch._real_branch
2751
def _vfs_set_option(self, value, name, section=None):
2752
return self._real_object()._get_config().set_option(
2753
value, name, section)
2756
class RemoteBzrDirConfig(RemoteConfig):
2757
"""A RemoteConfig for BzrDirs."""
2759
def __init__(self, bzrdir):
2760
self._bzrdir = bzrdir
2762
def _get_configobj(self):
2763
medium = self._bzrdir._client._medium
2764
verb = 'BzrDir.get_config_file'
2765
if medium._is_remote_before((1, 15)):
2766
raise errors.UnknownSmartMethod(verb)
2767
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2768
response = self._bzrdir._call_expecting_body(
2770
return self._response_to_configobj(response)
2772
def _vfs_get_option(self, name, section, default):
2773
return self._real_object()._get_config().get_option(
2774
name, section, default)
2776
def set_option(self, value, name, section=None):
2777
"""Set the value associated with a named option.
2779
:param value: The value to set
2780
:param name: The name of the value to set
2781
:param section: The section the option is in (if any)
2783
return self._real_object()._get_config().set_option(
2784
value, name, section)
2786
def _real_object(self):
2787
self._bzrdir._ensure_real()
2788
return self._bzrdir._real_bzrdir
2792
def _extract_tar(tar, to_dir):
2793
"""Extract all the contents of a tarfile object.
2795
A replacement for extractall, which is not present in python2.4
2798
tar.extract(tarinfo, to_dir)
2801
def _translate_error(err, **context):
2802
"""Translate an ErrorFromSmartServer into a more useful error.
2804
Possible context keys:
2812
If the error from the server doesn't match a known pattern, then
2813
UnknownErrorFromSmartServer is raised.
2817
return context[name]
2818
except KeyError, key_err:
2819
mutter('Missing key %r in context %r', key_err.args[0], context)
2822
"""Get the path from the context if present, otherwise use first error
2826
return context['path']
2827
except KeyError, key_err:
2829
return err.error_args[0]
2830
except IndexError, idx_err:
2832
'Missing key %r in context %r', key_err.args[0], context)
2835
if err.error_verb == 'IncompatibleRepositories':
2836
raise errors.IncompatibleRepositories(err.error_args[0],
2837
err.error_args[1], err.error_args[2])
2838
elif err.error_verb == 'NoSuchRevision':
2839
raise NoSuchRevision(find('branch'), err.error_args[0])
2840
elif err.error_verb == 'nosuchrevision':
2841
raise NoSuchRevision(find('repository'), err.error_args[0])
2842
elif err.error_verb == 'nobranch':
2843
if len(err.error_args) >= 1:
2844
extra = err.error_args[0]
2847
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2849
elif err.error_verb == 'norepository':
2850
raise errors.NoRepositoryPresent(find('bzrdir'))
2851
elif err.error_verb == 'LockContention':
2852
raise errors.LockContention('(remote lock)')
2853
elif err.error_verb == 'UnlockableTransport':
2854
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2855
elif err.error_verb == 'LockFailed':
2856
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2857
elif err.error_verb == 'TokenMismatch':
2858
raise errors.TokenMismatch(find('token'), '(remote token)')
2859
elif err.error_verb == 'Diverged':
2860
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2861
elif err.error_verb == 'TipChangeRejected':
2862
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2863
elif err.error_verb == 'UnstackableBranchFormat':
2864
raise errors.UnstackableBranchFormat(*err.error_args)
2865
elif err.error_verb == 'UnstackableRepositoryFormat':
2866
raise errors.UnstackableRepositoryFormat(*err.error_args)
2867
elif err.error_verb == 'NotStacked':
2868
raise errors.NotStacked(branch=find('branch'))
2869
elif err.error_verb == 'PermissionDenied':
2871
if len(err.error_args) >= 2:
2872
extra = err.error_args[1]
2875
raise errors.PermissionDenied(path, extra=extra)
2876
elif err.error_verb == 'ReadError':
2878
raise errors.ReadError(path)
2879
elif err.error_verb == 'NoSuchFile':
2881
raise errors.NoSuchFile(path)
2882
elif err.error_verb == 'FileExists':
2883
raise errors.FileExists(err.error_args[0])
2884
elif err.error_verb == 'DirectoryNotEmpty':
2885
raise errors.DirectoryNotEmpty(err.error_args[0])
2886
elif err.error_verb == 'ShortReadvError':
2887
args = err.error_args
2888
raise errors.ShortReadvError(
2889
args[0], int(args[1]), int(args[2]), int(args[3]))
2890
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2891
encoding = str(err.error_args[0]) # encoding must always be a string
2892
val = err.error_args[1]
2893
start = int(err.error_args[2])
2894
end = int(err.error_args[3])
2895
reason = str(err.error_args[4]) # reason must always be a string
2896
if val.startswith('u:'):
2897
val = val[2:].decode('utf-8')
2898
elif val.startswith('s:'):
2899
val = val[2:].decode('base64')
2900
if err.error_verb == 'UnicodeDecodeError':
2901
raise UnicodeDecodeError(encoding, val, start, end, reason)
2902
elif err.error_verb == 'UnicodeEncodeError':
2903
raise UnicodeEncodeError(encoding, val, start, end, reason)
2904
elif err.error_verb == 'ReadOnlyError':
2905
raise errors.TransportNotPossible('readonly transport')
2906
raise errors.UnknownErrorFromSmartServer(err)