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,
1239
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1240
delta, new_revision_id, parents)
1242
def add_revision(self, rev_id, rev, inv=None, config=None):
1244
return self._real_repository.add_revision(
1245
rev_id, rev, inv=inv, config=config)
1248
def get_inventory(self, revision_id):
1250
return self._real_repository.get_inventory(revision_id)
1252
def iter_inventories(self, revision_ids, ordering=None):
1254
return self._real_repository.iter_inventories(revision_ids, ordering)
1257
def get_revision(self, revision_id):
1259
return self._real_repository.get_revision(revision_id)
1261
def get_transaction(self):
1263
return self._real_repository.get_transaction()
1266
def clone(self, a_bzrdir, revision_id=None):
1268
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1270
def make_working_trees(self):
1271
"""See Repository.make_working_trees"""
1273
return self._real_repository.make_working_trees()
1275
def refresh_data(self):
1276
"""Re-read any data needed to to synchronise with disk.
1278
This method is intended to be called after another repository instance
1279
(such as one used by a smart server) has inserted data into the
1280
repository. It may not be called during a write group, but may be
1281
called at any other time.
1283
if self.is_in_write_group():
1284
raise errors.InternalBzrError(
1285
"May not refresh_data while in a write group.")
1286
if self._real_repository is not None:
1287
self._real_repository.refresh_data()
1289
def revision_ids_to_search_result(self, result_set):
1290
"""Convert a set of revision ids to a graph SearchResult."""
1291
result_parents = set()
1292
for parents in self.get_graph().get_parent_map(
1293
result_set).itervalues():
1294
result_parents.update(parents)
1295
included_keys = result_set.intersection(result_parents)
1296
start_keys = result_set.difference(included_keys)
1297
exclude_keys = result_parents.difference(result_set)
1298
result = graph.SearchResult(start_keys, exclude_keys,
1299
len(result_set), result_set)
1303
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1304
"""Return the revision ids that other has that this does not.
1306
These are returned in topological order.
1308
revision_id: only return revision ids included by revision_id.
1310
return repository.InterRepository.get(
1311
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1313
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1315
# No base implementation to use as RemoteRepository is not a subclass
1316
# of Repository; so this is a copy of Repository.fetch().
1317
if fetch_spec is not None and revision_id is not None:
1318
raise AssertionError(
1319
"fetch_spec and revision_id are mutually exclusive.")
1320
if self.is_in_write_group():
1321
raise errors.InternalBzrError(
1322
"May not fetch while in a write group.")
1323
# fast path same-url fetch operations
1324
if (self.has_same_location(source)
1325
and fetch_spec is None
1326
and self._has_same_fallbacks(source)):
1327
# check that last_revision is in 'from' and then return a
1329
if (revision_id is not None and
1330
not revision.is_null(revision_id)):
1331
self.get_revision(revision_id)
1333
# if there is no specific appropriate InterRepository, this will get
1334
# the InterRepository base class, which raises an
1335
# IncompatibleRepositories when asked to fetch.
1336
inter = repository.InterRepository.get(source, self)
1337
return inter.fetch(revision_id=revision_id, pb=pb,
1338
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1340
def create_bundle(self, target, base, fileobj, format=None):
1342
self._real_repository.create_bundle(target, base, fileobj, format)
1345
def get_ancestry(self, revision_id, topo_sorted=True):
1347
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1349
def fileids_altered_by_revision_ids(self, revision_ids):
1351
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1353
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1355
return self._real_repository._get_versioned_file_checker(
1356
revisions, revision_versions_cache)
1358
def iter_files_bytes(self, desired_files):
1359
"""See Repository.iter_file_bytes.
1362
return self._real_repository.iter_files_bytes(desired_files)
1364
def get_parent_map(self, revision_ids):
1365
"""See bzrlib.Graph.get_parent_map()."""
1366
return self._make_parents_provider().get_parent_map(revision_ids)
1368
def _get_parent_map_rpc(self, keys):
1369
"""Helper for get_parent_map that performs the RPC."""
1370
medium = self._client._medium
1371
if medium._is_remote_before((1, 2)):
1372
# We already found out that the server can't understand
1373
# Repository.get_parent_map requests, so just fetch the whole
1376
# Note that this reads the whole graph, when only some keys are
1377
# wanted. On this old server there's no way (?) to get them all
1378
# in one go, and the user probably will have seen a warning about
1379
# the server being old anyhow.
1380
rg = self._get_revision_graph(None)
1381
# There is an API discrepancy between get_parent_map and
1382
# get_revision_graph. Specifically, a "key:()" pair in
1383
# get_revision_graph just means a node has no parents. For
1384
# "get_parent_map" it means the node is a ghost. So fix up the
1385
# graph to correct this.
1386
# https://bugs.launchpad.net/bzr/+bug/214894
1387
# There is one other "bug" which is that ghosts in
1388
# get_revision_graph() are not returned at all. But we won't worry
1389
# about that for now.
1390
for node_id, parent_ids in rg.iteritems():
1391
if parent_ids == ():
1392
rg[node_id] = (NULL_REVISION,)
1393
rg[NULL_REVISION] = ()
1398
raise ValueError('get_parent_map(None) is not valid')
1399
if NULL_REVISION in keys:
1400
keys.discard(NULL_REVISION)
1401
found_parents = {NULL_REVISION:()}
1403
return found_parents
1406
# TODO(Needs analysis): We could assume that the keys being requested
1407
# from get_parent_map are in a breadth first search, so typically they
1408
# will all be depth N from some common parent, and we don't have to
1409
# have the server iterate from the root parent, but rather from the
1410
# keys we're searching; and just tell the server the keyspace we
1411
# already have; but this may be more traffic again.
1413
# Transform self._parents_map into a search request recipe.
1414
# TODO: Manage this incrementally to avoid covering the same path
1415
# repeatedly. (The server will have to on each request, but the less
1416
# work done the better).
1418
# Negative caching notes:
1419
# new server sends missing when a request including the revid
1420
# 'include-missing:' is present in the request.
1421
# missing keys are serialised as missing:X, and we then call
1422
# provider.note_missing(X) for-all X
1423
parents_map = self._unstacked_provider.get_cached_map()
1424
if parents_map is None:
1425
# Repository is not locked, so there's no cache.
1427
# start_set is all the keys in the cache
1428
start_set = set(parents_map)
1429
# result set is all the references to keys in the cache
1430
result_parents = set()
1431
for parents in parents_map.itervalues():
1432
result_parents.update(parents)
1433
stop_keys = result_parents.difference(start_set)
1434
# We don't need to send ghosts back to the server as a position to
1436
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1437
key_count = len(parents_map)
1438
if (NULL_REVISION in result_parents
1439
and NULL_REVISION in self._unstacked_provider.missing_keys):
1440
# If we pruned NULL_REVISION from the stop_keys because it's also
1441
# in our cache of "missing" keys we need to increment our key count
1442
# by 1, because the reconsitituted SearchResult on the server will
1443
# still consider NULL_REVISION to be an included key.
1445
included_keys = start_set.intersection(result_parents)
1446
start_set.difference_update(included_keys)
1447
recipe = ('manual', start_set, stop_keys, key_count)
1448
body = self._serialise_search_recipe(recipe)
1449
path = self.bzrdir._path_for_remote_call(self._client)
1451
if type(key) is not str:
1453
"key %r not a plain string" % (key,))
1454
verb = 'Repository.get_parent_map'
1455
args = (path, 'include-missing:') + tuple(keys)
1457
response = self._call_with_body_bytes_expecting_body(
1459
except errors.UnknownSmartMethod:
1460
# Server does not support this method, so get the whole graph.
1461
# Worse, we have to force a disconnection, because the server now
1462
# doesn't realise it has a body on the wire to consume, so the
1463
# only way to recover is to abandon the connection.
1465
'Server is too old for fast get_parent_map, reconnecting. '
1466
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1468
# To avoid having to disconnect repeatedly, we keep track of the
1469
# fact the server doesn't understand remote methods added in 1.2.
1470
medium._remember_remote_is_before((1, 2))
1471
# Recurse just once and we should use the fallback code.
1472
return self._get_parent_map_rpc(keys)
1473
response_tuple, response_handler = response
1474
if response_tuple[0] not in ['ok']:
1475
response_handler.cancel_read_body()
1476
raise errors.UnexpectedSmartServerResponse(response_tuple)
1477
if response_tuple[0] == 'ok':
1478
coded = bz2.decompress(response_handler.read_body_bytes())
1480
# no revisions found
1482
lines = coded.split('\n')
1485
d = tuple(line.split())
1487
revision_graph[d[0]] = d[1:]
1490
if d[0].startswith('missing:'):
1492
self._unstacked_provider.note_missing_key(revid)
1494
# no parents - so give the Graph result
1496
revision_graph[d[0]] = (NULL_REVISION,)
1497
return revision_graph
1500
def get_signature_text(self, revision_id):
1502
return self._real_repository.get_signature_text(revision_id)
1505
def _get_inventory_xml(self, revision_id):
1507
return self._real_repository._get_inventory_xml(revision_id)
1509
def reconcile(self, other=None, thorough=False):
1511
return self._real_repository.reconcile(other=other, thorough=thorough)
1513
def all_revision_ids(self):
1515
return self._real_repository.all_revision_ids()
1518
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1520
return self._real_repository.get_deltas_for_revisions(revisions,
1521
specific_fileids=specific_fileids)
1524
def get_revision_delta(self, revision_id, specific_fileids=None):
1526
return self._real_repository.get_revision_delta(revision_id,
1527
specific_fileids=specific_fileids)
1530
def revision_trees(self, revision_ids):
1532
return self._real_repository.revision_trees(revision_ids)
1535
def get_revision_reconcile(self, revision_id):
1537
return self._real_repository.get_revision_reconcile(revision_id)
1540
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1542
return self._real_repository.check(revision_ids=revision_ids,
1543
callback_refs=callback_refs, check_repo=check_repo)
1545
def copy_content_into(self, destination, revision_id=None):
1547
return self._real_repository.copy_content_into(
1548
destination, revision_id=revision_id)
1550
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1551
# get a tarball of the remote repository, and copy from that into the
1553
from bzrlib import osutils
1555
# TODO: Maybe a progress bar while streaming the tarball?
1556
note("Copying repository content as tarball...")
1557
tar_file = self._get_tarball('bz2')
1558
if tar_file is None:
1560
destination = to_bzrdir.create_repository()
1562
tar = tarfile.open('repository', fileobj=tar_file,
1564
tmpdir = osutils.mkdtemp()
1566
_extract_tar(tar, tmpdir)
1567
tmp_bzrdir = BzrDir.open(tmpdir)
1568
tmp_repo = tmp_bzrdir.open_repository()
1569
tmp_repo.copy_content_into(destination, revision_id)
1571
osutils.rmtree(tmpdir)
1575
# TODO: Suggestion from john: using external tar is much faster than
1576
# python's tarfile library, but it may not work on windows.
1579
def inventories(self):
1580
"""Decorate the real repository for now.
1582
In the long term a full blown network facility is needed to
1583
avoid creating a real repository object locally.
1586
return self._real_repository.inventories
1589
def pack(self, hint=None):
1590
"""Compress the data within the repository.
1592
This is not currently implemented within the smart server.
1595
return self._real_repository.pack(hint=hint)
1598
def revisions(self):
1599
"""Decorate the real repository for now.
1601
In the short term this should become a real object to intercept graph
1604
In the long term a full blown network facility is needed.
1607
return self._real_repository.revisions
1609
def set_make_working_trees(self, new_value):
1611
new_value_str = "True"
1613
new_value_str = "False"
1614
path = self.bzrdir._path_for_remote_call(self._client)
1616
response = self._call(
1617
'Repository.set_make_working_trees', path, new_value_str)
1618
except errors.UnknownSmartMethod:
1620
self._real_repository.set_make_working_trees(new_value)
1622
if response[0] != 'ok':
1623
raise errors.UnexpectedSmartServerResponse(response)
1626
def signatures(self):
1627
"""Decorate the real repository for now.
1629
In the long term a full blown network facility is needed to avoid
1630
creating a real repository object locally.
1633
return self._real_repository.signatures
1636
def sign_revision(self, revision_id, gpg_strategy):
1638
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1642
"""Decorate the real repository for now.
1644
In the long term a full blown network facility is needed to avoid
1645
creating a real repository object locally.
1648
return self._real_repository.texts
1651
def get_revisions(self, revision_ids):
1653
return self._real_repository.get_revisions(revision_ids)
1655
def supports_rich_root(self):
1656
return self._format.rich_root_data
1658
def iter_reverse_revision_history(self, revision_id):
1660
return self._real_repository.iter_reverse_revision_history(revision_id)
1663
def _serializer(self):
1664
return self._format._serializer
1666
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1668
return self._real_repository.store_revision_signature(
1669
gpg_strategy, plaintext, revision_id)
1671
def add_signature_text(self, revision_id, signature):
1673
return self._real_repository.add_signature_text(revision_id, signature)
1675
def has_signature_for_revision_id(self, revision_id):
1677
return self._real_repository.has_signature_for_revision_id(revision_id)
1679
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1681
return self._real_repository.item_keys_introduced_by(revision_ids,
1682
_files_pb=_files_pb)
1684
def revision_graph_can_have_wrong_parents(self):
1685
# The answer depends on the remote repo format.
1687
return self._real_repository.revision_graph_can_have_wrong_parents()
1689
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1691
return self._real_repository._find_inconsistent_revision_parents(
1694
def _check_for_inconsistent_revision_parents(self):
1696
return self._real_repository._check_for_inconsistent_revision_parents()
1698
def _make_parents_provider(self, other=None):
1699
providers = [self._unstacked_provider]
1700
if other is not None:
1701
providers.insert(0, other)
1702
providers.extend(r._make_parents_provider() for r in
1703
self._fallback_repositories)
1704
return graph.StackedParentsProvider(providers)
1706
def _serialise_search_recipe(self, recipe):
1707
"""Serialise a graph search recipe.
1709
:param recipe: A search recipe (start, stop, count).
1710
:return: Serialised bytes.
1712
start_keys = ' '.join(recipe[1])
1713
stop_keys = ' '.join(recipe[2])
1714
count = str(recipe[3])
1715
return '\n'.join((start_keys, stop_keys, count))
1717
def _serialise_search_result(self, search_result):
1718
if isinstance(search_result, graph.PendingAncestryResult):
1719
parts = ['ancestry-of']
1720
parts.extend(search_result.heads)
1722
recipe = search_result.get_recipe()
1723
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1724
return '\n'.join(parts)
1727
path = self.bzrdir._path_for_remote_call(self._client)
1729
response = self._call('PackRepository.autopack', path)
1730
except errors.UnknownSmartMethod:
1732
self._real_repository._pack_collection.autopack()
1735
if response[0] != 'ok':
1736
raise errors.UnexpectedSmartServerResponse(response)
1739
class RemoteStreamSink(repository.StreamSink):
1741
def _insert_real(self, stream, src_format, resume_tokens):
1742
self.target_repo._ensure_real()
1743
sink = self.target_repo._real_repository._get_sink()
1744
result = sink.insert_stream(stream, src_format, resume_tokens)
1746
self.target_repo.autopack()
1749
def insert_stream(self, stream, src_format, resume_tokens):
1750
target = self.target_repo
1751
target._unstacked_provider.missing_keys.clear()
1752
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1753
if target._lock_token:
1754
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1755
lock_args = (target._lock_token or '',)
1757
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1759
client = target._client
1760
medium = client._medium
1761
path = target.bzrdir._path_for_remote_call(client)
1762
# Probe for the verb to use with an empty stream before sending the
1763
# real stream to it. We do this both to avoid the risk of sending a
1764
# large request that is then rejected, and because we don't want to
1765
# implement a way to buffer, rewind, or restart the stream.
1767
for verb, required_version in candidate_calls:
1768
if medium._is_remote_before(required_version):
1771
# We've already done the probing (and set _is_remote_before) on
1772
# a previous insert.
1775
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1777
response = client.call_with_body_stream(
1778
(verb, path, '') + lock_args, byte_stream)
1779
except errors.UnknownSmartMethod:
1780
medium._remember_remote_is_before(required_version)
1786
return self._insert_real(stream, src_format, resume_tokens)
1787
self._last_inv_record = None
1788
self._last_substream = None
1789
if required_version < (1, 19):
1790
# Remote side doesn't support inventory deltas. Wrap the stream to
1791
# make sure we don't send any. If the stream contains inventory
1792
# deltas we'll interrupt the smart insert_stream request and
1794
stream = self._stop_stream_if_inventory_delta(stream)
1795
byte_stream = smart_repo._stream_to_byte_stream(
1797
resume_tokens = ' '.join(resume_tokens)
1798
response = client.call_with_body_stream(
1799
(verb, path, resume_tokens) + lock_args, byte_stream)
1800
if response[0][0] not in ('ok', 'missing-basis'):
1801
raise errors.UnexpectedSmartServerResponse(response)
1802
if self._last_substream is not None:
1803
# The stream included an inventory-delta record, but the remote
1804
# side isn't new enough to support them. So we need to send the
1805
# rest of the stream via VFS.
1806
self.target_repo.refresh_data()
1807
return self._resume_stream_with_vfs(response, src_format)
1808
if response[0][0] == 'missing-basis':
1809
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1810
resume_tokens = tokens
1811
return resume_tokens, set(missing_keys)
1813
self.target_repo.refresh_data()
1816
def _resume_stream_with_vfs(self, response, src_format):
1817
"""Resume sending a stream via VFS, first resending the record and
1818
substream that couldn't be sent via an insert_stream verb.
1820
if response[0][0] == 'missing-basis':
1821
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1822
# Ignore missing_keys, we haven't finished inserting yet
1825
def resume_substream():
1826
# Yield the substream that was interrupted.
1827
for record in self._last_substream:
1829
self._last_substream = None
1830
def resume_stream():
1831
# Finish sending the interrupted substream
1832
yield ('inventory-deltas', resume_substream())
1833
# Then simply continue sending the rest of the stream.
1834
for substream_kind, substream in self._last_stream:
1835
yield substream_kind, substream
1836
return self._insert_real(resume_stream(), src_format, tokens)
1838
def _stop_stream_if_inventory_delta(self, stream):
1839
"""Normally this just lets the original stream pass-through unchanged.
1841
However if any 'inventory-deltas' substream occurs it will stop
1842
streaming, and store the interrupted substream and stream in
1843
self._last_substream and self._last_stream so that the stream can be
1844
resumed by _resume_stream_with_vfs.
1847
stream_iter = iter(stream)
1848
for substream_kind, substream in stream_iter:
1849
if substream_kind == 'inventory-deltas':
1850
self._last_substream = substream
1851
self._last_stream = stream_iter
1854
yield substream_kind, substream
1857
class RemoteStreamSource(repository.StreamSource):
1858
"""Stream data from a remote server."""
1860
def get_stream(self, search):
1861
if (self.from_repository._fallback_repositories and
1862
self.to_format._fetch_order == 'topological'):
1863
return self._real_stream(self.from_repository, search)
1866
repos = [self.from_repository]
1872
repos.extend(repo._fallback_repositories)
1873
sources.append(repo)
1874
return self.missing_parents_chain(search, sources)
1876
def get_stream_for_missing_keys(self, missing_keys):
1877
self.from_repository._ensure_real()
1878
real_repo = self.from_repository._real_repository
1879
real_source = real_repo._get_source(self.to_format)
1880
return real_source.get_stream_for_missing_keys(missing_keys)
1882
def _real_stream(self, repo, search):
1883
"""Get a stream for search from repo.
1885
This never called RemoteStreamSource.get_stream, and is a heler
1886
for RemoteStreamSource._get_stream to allow getting a stream
1887
reliably whether fallback back because of old servers or trying
1888
to stream from a non-RemoteRepository (which the stacked support
1891
source = repo._get_source(self.to_format)
1892
if isinstance(source, RemoteStreamSource):
1894
source = repo._real_repository._get_source(self.to_format)
1895
return source.get_stream(search)
1897
def _get_stream(self, repo, search):
1898
"""Core worker to get a stream from repo for search.
1900
This is used by both get_stream and the stacking support logic. It
1901
deliberately gets a stream for repo which does not need to be
1902
self.from_repository. In the event that repo is not Remote, or
1903
cannot do a smart stream, a fallback is made to the generic
1904
repository._get_stream() interface, via self._real_stream.
1906
In the event of stacking, streams from _get_stream will not
1907
contain all the data for search - this is normal (see get_stream).
1909
:param repo: A repository.
1910
:param search: A search.
1912
# Fallbacks may be non-smart
1913
if not isinstance(repo, RemoteRepository):
1914
return self._real_stream(repo, search)
1915
client = repo._client
1916
medium = client._medium
1917
path = repo.bzrdir._path_for_remote_call(client)
1918
search_bytes = repo._serialise_search_result(search)
1919
args = (path, self.to_format.network_name())
1921
('Repository.get_stream_1.19', (1, 19)),
1922
('Repository.get_stream', (1, 13))]
1924
for verb, version in candidate_verbs:
1925
if medium._is_remote_before(version):
1928
response = repo._call_with_body_bytes_expecting_body(
1929
verb, args, search_bytes)
1930
except errors.UnknownSmartMethod:
1931
medium._remember_remote_is_before(version)
1933
response_tuple, response_handler = response
1937
return self._real_stream(repo, search)
1938
if response_tuple[0] != 'ok':
1939
raise errors.UnexpectedSmartServerResponse(response_tuple)
1940
byte_stream = response_handler.read_streamed_body()
1941
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1942
if src_format.network_name() != repo._format.network_name():
1943
raise AssertionError(
1944
"Mismatched RemoteRepository and stream src %r, %r" % (
1945
src_format.network_name(), repo._format.network_name()))
1948
def missing_parents_chain(self, search, sources):
1949
"""Chain multiple streams together to handle stacking.
1951
:param search: The overall search to satisfy with streams.
1952
:param sources: A list of Repository objects to query.
1954
self.from_serialiser = self.from_repository._format._serializer
1955
self.seen_revs = set()
1956
self.referenced_revs = set()
1957
# If there are heads in the search, or the key count is > 0, we are not
1959
while not search.is_empty() and len(sources) > 1:
1960
source = sources.pop(0)
1961
stream = self._get_stream(source, search)
1962
for kind, substream in stream:
1963
if kind != 'revisions':
1964
yield kind, substream
1966
yield kind, self.missing_parents_rev_handler(substream)
1967
search = search.refine(self.seen_revs, self.referenced_revs)
1968
self.seen_revs = set()
1969
self.referenced_revs = set()
1970
if not search.is_empty():
1971
for kind, stream in self._get_stream(sources[0], search):
1974
def missing_parents_rev_handler(self, substream):
1975
for content in substream:
1976
revision_bytes = content.get_bytes_as('fulltext')
1977
revision = self.from_serialiser.read_revision_from_string(
1979
self.seen_revs.add(content.key[-1])
1980
self.referenced_revs.update(revision.parent_ids)
1984
class RemoteBranchLockableFiles(LockableFiles):
1985
"""A 'LockableFiles' implementation that talks to a smart server.
1987
This is not a public interface class.
1990
def __init__(self, bzrdir, _client):
1991
self.bzrdir = bzrdir
1992
self._client = _client
1993
self._need_find_modes = True
1994
LockableFiles.__init__(
1995
self, bzrdir.get_branch_transport(None),
1996
'lock', lockdir.LockDir)
1998
def _find_modes(self):
1999
# RemoteBranches don't let the client set the mode of control files.
2000
self._dir_mode = None
2001
self._file_mode = None
2004
class RemoteBranchFormat(branch.BranchFormat):
2006
def __init__(self, network_name=None):
2007
super(RemoteBranchFormat, self).__init__()
2008
self._matchingbzrdir = RemoteBzrDirFormat()
2009
self._matchingbzrdir.set_branch_format(self)
2010
self._custom_format = None
2011
self._network_name = network_name
2013
def __eq__(self, other):
2014
return (isinstance(other, RemoteBranchFormat) and
2015
self.__dict__ == other.__dict__)
2017
def _ensure_real(self):
2018
if self._custom_format is None:
2019
self._custom_format = branch.network_format_registry.get(
2022
def get_format_description(self):
2024
return 'Remote: ' + self._custom_format.get_format_description()
2026
def network_name(self):
2027
return self._network_name
2029
def open(self, a_bzrdir, ignore_fallbacks=False):
2030
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2032
def _vfs_initialize(self, a_bzrdir):
2033
# Initialisation when using a local bzrdir object, or a non-vfs init
2034
# method is not available on the server.
2035
# self._custom_format is always set - the start of initialize ensures
2037
if isinstance(a_bzrdir, RemoteBzrDir):
2038
a_bzrdir._ensure_real()
2039
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2041
# We assume the bzrdir is parameterised; it may not be.
2042
result = self._custom_format.initialize(a_bzrdir)
2043
if (isinstance(a_bzrdir, RemoteBzrDir) and
2044
not isinstance(result, RemoteBranch)):
2045
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2048
def initialize(self, a_bzrdir):
2049
# 1) get the network name to use.
2050
if self._custom_format:
2051
network_name = self._custom_format.network_name()
2053
# Select the current bzrlib default and ask for that.
2054
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2055
reference_format = reference_bzrdir_format.get_branch_format()
2056
self._custom_format = reference_format
2057
network_name = reference_format.network_name()
2058
# Being asked to create on a non RemoteBzrDir:
2059
if not isinstance(a_bzrdir, RemoteBzrDir):
2060
return self._vfs_initialize(a_bzrdir)
2061
medium = a_bzrdir._client._medium
2062
if medium._is_remote_before((1, 13)):
2063
return self._vfs_initialize(a_bzrdir)
2064
# Creating on a remote bzr dir.
2065
# 2) try direct creation via RPC
2066
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2067
verb = 'BzrDir.create_branch'
2069
response = a_bzrdir._call(verb, path, network_name)
2070
except errors.UnknownSmartMethod:
2071
# Fallback - use vfs methods
2072
medium._remember_remote_is_before((1, 13))
2073
return self._vfs_initialize(a_bzrdir)
2074
if response[0] != 'ok':
2075
raise errors.UnexpectedSmartServerResponse(response)
2076
# Turn the response into a RemoteRepository object.
2077
format = RemoteBranchFormat(network_name=response[1])
2078
repo_format = response_tuple_to_repo_format(response[3:])
2079
if response[2] == '':
2080
repo_bzrdir = a_bzrdir
2082
repo_bzrdir = RemoteBzrDir(
2083
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2085
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2086
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2087
format=format, setup_stacking=False)
2088
# XXX: We know this is a new branch, so it must have revno 0, revid
2089
# NULL_REVISION. Creating the branch locked would make this be unable
2090
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2091
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2092
return remote_branch
2094
def make_tags(self, branch):
2096
return self._custom_format.make_tags(branch)
2098
def supports_tags(self):
2099
# Remote branches might support tags, but we won't know until we
2100
# access the real remote branch.
2102
return self._custom_format.supports_tags()
2104
def supports_stacking(self):
2106
return self._custom_format.supports_stacking()
2108
def supports_set_append_revisions_only(self):
2110
return self._custom_format.supports_set_append_revisions_only()
2113
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2114
"""Branch stored on a server accessed by HPSS RPC.
2116
At the moment most operations are mapped down to simple file operations.
2119
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2120
_client=None, format=None, setup_stacking=True):
2121
"""Create a RemoteBranch instance.
2123
:param real_branch: An optional local implementation of the branch
2124
format, usually accessing the data via the VFS.
2125
:param _client: Private parameter for testing.
2126
:param format: A RemoteBranchFormat object, None to create one
2127
automatically. If supplied it should have a network_name already
2129
:param setup_stacking: If True make an RPC call to determine the
2130
stacked (or not) status of the branch. If False assume the branch
2133
# We intentionally don't call the parent class's __init__, because it
2134
# will try to assign to self.tags, which is a property in this subclass.
2135
# And the parent's __init__ doesn't do much anyway.
2136
self.bzrdir = remote_bzrdir
2137
if _client is not None:
2138
self._client = _client
2140
self._client = remote_bzrdir._client
2141
self.repository = remote_repository
2142
if real_branch is not None:
2143
self._real_branch = real_branch
2144
# Give the remote repository the matching real repo.
2145
real_repo = self._real_branch.repository
2146
if isinstance(real_repo, RemoteRepository):
2147
real_repo._ensure_real()
2148
real_repo = real_repo._real_repository
2149
self.repository._set_real_repository(real_repo)
2150
# Give the branch the remote repository to let fast-pathing happen.
2151
self._real_branch.repository = self.repository
2153
self._real_branch = None
2154
# Fill out expected attributes of branch for bzrlib API users.
2155
self._clear_cached_state()
2156
self.base = self.bzrdir.root_transport.base
2157
self._control_files = None
2158
self._lock_mode = None
2159
self._lock_token = None
2160
self._repo_lock_token = None
2161
self._lock_count = 0
2162
self._leave_lock = False
2163
# Setup a format: note that we cannot call _ensure_real until all the
2164
# attributes above are set: This code cannot be moved higher up in this
2167
self._format = RemoteBranchFormat()
2168
if real_branch is not None:
2169
self._format._network_name = \
2170
self._real_branch._format.network_name()
2172
self._format = format
2173
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2174
# branch.open_branch method.
2175
self._real_ignore_fallbacks = not setup_stacking
2176
if not self._format._network_name:
2177
# Did not get from open_branchV2 - old server.
2179
self._format._network_name = \
2180
self._real_branch._format.network_name()
2181
self.tags = self._format.make_tags(self)
2182
# The base class init is not called, so we duplicate this:
2183
hooks = branch.Branch.hooks['open']
2186
self._is_stacked = False
2188
self._setup_stacking()
2190
def _setup_stacking(self):
2191
# configure stacking into the remote repository, by reading it from
2194
fallback_url = self.get_stacked_on_url()
2195
except (errors.NotStacked, errors.UnstackableBranchFormat,
2196
errors.UnstackableRepositoryFormat), e:
2198
self._is_stacked = True
2199
self._activate_fallback_location(fallback_url)
2201
def _get_config(self):
2202
return RemoteBranchConfig(self)
2204
def _get_real_transport(self):
2205
# if we try vfs access, return the real branch's vfs transport
2207
return self._real_branch._transport
2209
_transport = property(_get_real_transport)
2212
return "%s(%s)" % (self.__class__.__name__, self.base)
2216
def _ensure_real(self):
2217
"""Ensure that there is a _real_branch set.
2219
Used before calls to self._real_branch.
2221
if self._real_branch is None:
2222
if not vfs.vfs_enabled():
2223
raise AssertionError('smart server vfs must be enabled '
2224
'to use vfs implementation')
2225
self.bzrdir._ensure_real()
2226
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2227
ignore_fallbacks=self._real_ignore_fallbacks)
2228
if self.repository._real_repository is None:
2229
# Give the remote repository the matching real repo.
2230
real_repo = self._real_branch.repository
2231
if isinstance(real_repo, RemoteRepository):
2232
real_repo._ensure_real()
2233
real_repo = real_repo._real_repository
2234
self.repository._set_real_repository(real_repo)
2235
# Give the real branch the remote repository to let fast-pathing
2237
self._real_branch.repository = self.repository
2238
if self._lock_mode == 'r':
2239
self._real_branch.lock_read()
2240
elif self._lock_mode == 'w':
2241
self._real_branch.lock_write(token=self._lock_token)
2243
def _translate_error(self, err, **context):
2244
self.repository._translate_error(err, branch=self, **context)
2246
def _clear_cached_state(self):
2247
super(RemoteBranch, self)._clear_cached_state()
2248
if self._real_branch is not None:
2249
self._real_branch._clear_cached_state()
2251
def _clear_cached_state_of_remote_branch_only(self):
2252
"""Like _clear_cached_state, but doesn't clear the cache of
2255
This is useful when falling back to calling a method of
2256
self._real_branch that changes state. In that case the underlying
2257
branch changes, so we need to invalidate this RemoteBranch's cache of
2258
it. However, there's no need to invalidate the _real_branch's cache
2259
too, in fact doing so might harm performance.
2261
super(RemoteBranch, self)._clear_cached_state()
2264
def control_files(self):
2265
# Defer actually creating RemoteBranchLockableFiles until its needed,
2266
# because it triggers an _ensure_real that we otherwise might not need.
2267
if self._control_files is None:
2268
self._control_files = RemoteBranchLockableFiles(
2269
self.bzrdir, self._client)
2270
return self._control_files
2272
def _get_checkout_format(self):
2274
return self._real_branch._get_checkout_format()
2276
def get_physical_lock_status(self):
2277
"""See Branch.get_physical_lock_status()."""
2278
# should be an API call to the server, as branches must be lockable.
2280
return self._real_branch.get_physical_lock_status()
2282
def get_stacked_on_url(self):
2283
"""Get the URL this branch is stacked against.
2285
:raises NotStacked: If the branch is not stacked.
2286
:raises UnstackableBranchFormat: If the branch does not support
2288
:raises UnstackableRepositoryFormat: If the repository does not support
2292
# there may not be a repository yet, so we can't use
2293
# self._translate_error, so we can't use self._call either.
2294
response = self._client.call('Branch.get_stacked_on_url',
2295
self._remote_path())
2296
except errors.ErrorFromSmartServer, err:
2297
# there may not be a repository yet, so we can't call through
2298
# its _translate_error
2299
_translate_error(err, branch=self)
2300
except errors.UnknownSmartMethod, err:
2302
return self._real_branch.get_stacked_on_url()
2303
if response[0] != 'ok':
2304
raise errors.UnexpectedSmartServerResponse(response)
2307
def set_stacked_on_url(self, url):
2308
branch.Branch.set_stacked_on_url(self, url)
2310
self._is_stacked = False
2312
self._is_stacked = True
2314
def _vfs_get_tags_bytes(self):
2316
return self._real_branch._get_tags_bytes()
2318
def _get_tags_bytes(self):
2319
medium = self._client._medium
2320
if medium._is_remote_before((1, 13)):
2321
return self._vfs_get_tags_bytes()
2323
response = self._call('Branch.get_tags_bytes', self._remote_path())
2324
except errors.UnknownSmartMethod:
2325
medium._remember_remote_is_before((1, 13))
2326
return self._vfs_get_tags_bytes()
2329
def _vfs_set_tags_bytes(self, bytes):
2331
return self._real_branch._set_tags_bytes(bytes)
2333
def _set_tags_bytes(self, bytes):
2334
medium = self._client._medium
2335
if medium._is_remote_before((1, 18)):
2336
self._vfs_set_tags_bytes(bytes)
2340
self._remote_path(), self._lock_token, self._repo_lock_token)
2341
response = self._call_with_body_bytes(
2342
'Branch.set_tags_bytes', args, bytes)
2343
except errors.UnknownSmartMethod:
2344
medium._remember_remote_is_before((1, 18))
2345
self._vfs_set_tags_bytes(bytes)
2347
def lock_read(self):
2348
self.repository.lock_read()
2349
if not self._lock_mode:
2350
self._note_lock('r')
2351
self._lock_mode = 'r'
2352
self._lock_count = 1
2353
if self._real_branch is not None:
2354
self._real_branch.lock_read()
2356
self._lock_count += 1
2358
def _remote_lock_write(self, token):
2360
branch_token = repo_token = ''
2362
branch_token = token
2363
repo_token = self.repository.lock_write()
2364
self.repository.unlock()
2365
err_context = {'token': token}
2366
response = self._call(
2367
'Branch.lock_write', self._remote_path(), branch_token,
2368
repo_token or '', **err_context)
2369
if response[0] != 'ok':
2370
raise errors.UnexpectedSmartServerResponse(response)
2371
ok, branch_token, repo_token = response
2372
return branch_token, repo_token
2374
def lock_write(self, token=None):
2375
if not self._lock_mode:
2376
self._note_lock('w')
2377
# Lock the branch and repo in one remote call.
2378
remote_tokens = self._remote_lock_write(token)
2379
self._lock_token, self._repo_lock_token = remote_tokens
2380
if not self._lock_token:
2381
raise SmartProtocolError('Remote server did not return a token!')
2382
# Tell the self.repository object that it is locked.
2383
self.repository.lock_write(
2384
self._repo_lock_token, _skip_rpc=True)
2386
if self._real_branch is not None:
2387
self._real_branch.lock_write(token=self._lock_token)
2388
if token is not None:
2389
self._leave_lock = True
2391
self._leave_lock = False
2392
self._lock_mode = 'w'
2393
self._lock_count = 1
2394
elif self._lock_mode == 'r':
2395
raise errors.ReadOnlyTransaction
2397
if token is not None:
2398
# A token was given to lock_write, and we're relocking, so
2399
# check that the given token actually matches the one we
2401
if token != self._lock_token:
2402
raise errors.TokenMismatch(token, self._lock_token)
2403
self._lock_count += 1
2404
# Re-lock the repository too.
2405
self.repository.lock_write(self._repo_lock_token)
2406
return self._lock_token or None
2408
def _unlock(self, branch_token, repo_token):
2409
err_context = {'token': str((branch_token, repo_token))}
2410
response = self._call(
2411
'Branch.unlock', self._remote_path(), branch_token,
2412
repo_token or '', **err_context)
2413
if response == ('ok',):
2415
raise errors.UnexpectedSmartServerResponse(response)
2417
@only_raises(errors.LockNotHeld, errors.LockBroken)
2420
self._lock_count -= 1
2421
if not self._lock_count:
2422
self._clear_cached_state()
2423
mode = self._lock_mode
2424
self._lock_mode = None
2425
if self._real_branch is not None:
2426
if (not self._leave_lock and mode == 'w' and
2427
self._repo_lock_token):
2428
# If this RemoteBranch will remove the physical lock
2429
# for the repository, make sure the _real_branch
2430
# doesn't do it first. (Because the _real_branch's
2431
# repository is set to be the RemoteRepository.)
2432
self._real_branch.repository.leave_lock_in_place()
2433
self._real_branch.unlock()
2435
# Only write-locked branched need to make a remote method
2436
# call to perform the unlock.
2438
if not self._lock_token:
2439
raise AssertionError('Locked, but no token!')
2440
branch_token = self._lock_token
2441
repo_token = self._repo_lock_token
2442
self._lock_token = None
2443
self._repo_lock_token = None
2444
if not self._leave_lock:
2445
self._unlock(branch_token, repo_token)
2447
self.repository.unlock()
2449
def break_lock(self):
2451
return self._real_branch.break_lock()
2453
def leave_lock_in_place(self):
2454
if not self._lock_token:
2455
raise NotImplementedError(self.leave_lock_in_place)
2456
self._leave_lock = True
2458
def dont_leave_lock_in_place(self):
2459
if not self._lock_token:
2460
raise NotImplementedError(self.dont_leave_lock_in_place)
2461
self._leave_lock = False
2464
def get_rev_id(self, revno, history=None):
2466
return _mod_revision.NULL_REVISION
2467
last_revision_info = self.last_revision_info()
2468
ok, result = self.repository.get_rev_id_for_revno(
2469
revno, last_revision_info)
2472
missing_parent = result[1]
2473
# Either the revision named by the server is missing, or its parent
2474
# is. Call get_parent_map to determine which, so that we report a
2476
parent_map = self.repository.get_parent_map([missing_parent])
2477
if missing_parent in parent_map:
2478
missing_parent = parent_map[missing_parent]
2479
raise errors.RevisionNotPresent(missing_parent, self.repository)
2481
def _last_revision_info(self):
2482
response = self._call('Branch.last_revision_info', self._remote_path())
2483
if response[0] != 'ok':
2484
raise SmartProtocolError('unexpected response code %s' % (response,))
2485
revno = int(response[1])
2486
last_revision = response[2]
2487
return (revno, last_revision)
2489
def _gen_revision_history(self):
2490
"""See Branch._gen_revision_history()."""
2491
if self._is_stacked:
2493
return self._real_branch._gen_revision_history()
2494
response_tuple, response_handler = self._call_expecting_body(
2495
'Branch.revision_history', self._remote_path())
2496
if response_tuple[0] != 'ok':
2497
raise errors.UnexpectedSmartServerResponse(response_tuple)
2498
result = response_handler.read_body_bytes().split('\x00')
2503
def _remote_path(self):
2504
return self.bzrdir._path_for_remote_call(self._client)
2506
def _set_last_revision_descendant(self, revision_id, other_branch,
2507
allow_diverged=False, allow_overwrite_descendant=False):
2508
# This performs additional work to meet the hook contract; while its
2509
# undesirable, we have to synthesise the revno to call the hook, and
2510
# not calling the hook is worse as it means changes can't be prevented.
2511
# Having calculated this though, we can't just call into
2512
# set_last_revision_info as a simple call, because there is a set_rh
2513
# hook that some folk may still be using.
2514
old_revno, old_revid = self.last_revision_info()
2515
history = self._lefthand_history(revision_id)
2516
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2517
err_context = {'other_branch': other_branch}
2518
response = self._call('Branch.set_last_revision_ex',
2519
self._remote_path(), self._lock_token, self._repo_lock_token,
2520
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2522
self._clear_cached_state()
2523
if len(response) != 3 and response[0] != 'ok':
2524
raise errors.UnexpectedSmartServerResponse(response)
2525
new_revno, new_revision_id = response[1:]
2526
self._last_revision_info_cache = new_revno, new_revision_id
2527
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2528
if self._real_branch is not None:
2529
cache = new_revno, new_revision_id
2530
self._real_branch._last_revision_info_cache = cache
2532
def _set_last_revision(self, revision_id):
2533
old_revno, old_revid = self.last_revision_info()
2534
# This performs additional work to meet the hook contract; while its
2535
# undesirable, we have to synthesise the revno to call the hook, and
2536
# not calling the hook is worse as it means changes can't be prevented.
2537
# Having calculated this though, we can't just call into
2538
# set_last_revision_info as a simple call, because there is a set_rh
2539
# hook that some folk may still be using.
2540
history = self._lefthand_history(revision_id)
2541
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2542
self._clear_cached_state()
2543
response = self._call('Branch.set_last_revision',
2544
self._remote_path(), self._lock_token, self._repo_lock_token,
2546
if response != ('ok',):
2547
raise errors.UnexpectedSmartServerResponse(response)
2548
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2551
def set_revision_history(self, rev_history):
2552
# Send just the tip revision of the history; the server will generate
2553
# the full history from that. If the revision doesn't exist in this
2554
# branch, NoSuchRevision will be raised.
2555
if rev_history == []:
2558
rev_id = rev_history[-1]
2559
self._set_last_revision(rev_id)
2560
for hook in branch.Branch.hooks['set_rh']:
2561
hook(self, rev_history)
2562
self._cache_revision_history(rev_history)
2564
def _get_parent_location(self):
2565
medium = self._client._medium
2566
if medium._is_remote_before((1, 13)):
2567
return self._vfs_get_parent_location()
2569
response = self._call('Branch.get_parent', self._remote_path())
2570
except errors.UnknownSmartMethod:
2571
medium._remember_remote_is_before((1, 13))
2572
return self._vfs_get_parent_location()
2573
if len(response) != 1:
2574
raise errors.UnexpectedSmartServerResponse(response)
2575
parent_location = response[0]
2576
if parent_location == '':
2578
return parent_location
2580
def _vfs_get_parent_location(self):
2582
return self._real_branch._get_parent_location()
2584
def _set_parent_location(self, url):
2585
medium = self._client._medium
2586
if medium._is_remote_before((1, 15)):
2587
return self._vfs_set_parent_location(url)
2589
call_url = url or ''
2590
if type(call_url) is not str:
2591
raise AssertionError('url must be a str or None (%s)' % url)
2592
response = self._call('Branch.set_parent_location',
2593
self._remote_path(), self._lock_token, self._repo_lock_token,
2595
except errors.UnknownSmartMethod:
2596
medium._remember_remote_is_before((1, 15))
2597
return self._vfs_set_parent_location(url)
2599
raise errors.UnexpectedSmartServerResponse(response)
2601
def _vfs_set_parent_location(self, url):
2603
return self._real_branch._set_parent_location(url)
2606
def pull(self, source, overwrite=False, stop_revision=None,
2608
self._clear_cached_state_of_remote_branch_only()
2610
return self._real_branch.pull(
2611
source, overwrite=overwrite, stop_revision=stop_revision,
2612
_override_hook_target=self, **kwargs)
2615
def push(self, target, overwrite=False, stop_revision=None):
2617
return self._real_branch.push(
2618
target, overwrite=overwrite, stop_revision=stop_revision,
2619
_override_hook_source_branch=self)
2621
def is_locked(self):
2622
return self._lock_count >= 1
2625
def revision_id_to_revno(self, revision_id):
2627
return self._real_branch.revision_id_to_revno(revision_id)
2630
def set_last_revision_info(self, revno, revision_id):
2631
# XXX: These should be returned by the set_last_revision_info verb
2632
old_revno, old_revid = self.last_revision_info()
2633
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2634
revision_id = ensure_null(revision_id)
2636
response = self._call('Branch.set_last_revision_info',
2637
self._remote_path(), self._lock_token, self._repo_lock_token,
2638
str(revno), revision_id)
2639
except errors.UnknownSmartMethod:
2641
self._clear_cached_state_of_remote_branch_only()
2642
self._real_branch.set_last_revision_info(revno, revision_id)
2643
self._last_revision_info_cache = revno, revision_id
2645
if response == ('ok',):
2646
self._clear_cached_state()
2647
self._last_revision_info_cache = revno, revision_id
2648
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2649
# Update the _real_branch's cache too.
2650
if self._real_branch is not None:
2651
cache = self._last_revision_info_cache
2652
self._real_branch._last_revision_info_cache = cache
2654
raise errors.UnexpectedSmartServerResponse(response)
2657
def generate_revision_history(self, revision_id, last_rev=None,
2659
medium = self._client._medium
2660
if not medium._is_remote_before((1, 6)):
2661
# Use a smart method for 1.6 and above servers
2663
self._set_last_revision_descendant(revision_id, other_branch,
2664
allow_diverged=True, allow_overwrite_descendant=True)
2666
except errors.UnknownSmartMethod:
2667
medium._remember_remote_is_before((1, 6))
2668
self._clear_cached_state_of_remote_branch_only()
2669
self.set_revision_history(self._lefthand_history(revision_id,
2670
last_rev=last_rev,other_branch=other_branch))
2672
def set_push_location(self, location):
2674
return self._real_branch.set_push_location(location)
2677
class RemoteConfig(object):
2678
"""A Config that reads and writes from smart verbs.
2680
It is a low-level object that considers config data to be name/value pairs
2681
that may be associated with a section. Assigning meaning to the these
2682
values is done at higher levels like bzrlib.config.TreeConfig.
2685
def get_option(self, name, section=None, default=None):
2686
"""Return the value associated with a named option.
2688
:param name: The name of the value
2689
:param section: The section the option is in (if any)
2690
:param default: The value to return if the value is not set
2691
:return: The value or default value
2694
configobj = self._get_configobj()
2696
section_obj = configobj
2699
section_obj = configobj[section]
2702
return section_obj.get(name, default)
2703
except errors.UnknownSmartMethod:
2704
return self._vfs_get_option(name, section, default)
2706
def _response_to_configobj(self, response):
2707
if len(response[0]) and response[0][0] != 'ok':
2708
raise errors.UnexpectedSmartServerResponse(response)
2709
lines = response[1].read_body_bytes().splitlines()
2710
return config.ConfigObj(lines, encoding='utf-8')
2713
class RemoteBranchConfig(RemoteConfig):
2714
"""A RemoteConfig for Branches."""
2716
def __init__(self, branch):
2717
self._branch = branch
2719
def _get_configobj(self):
2720
path = self._branch._remote_path()
2721
response = self._branch._client.call_expecting_body(
2722
'Branch.get_config_file', path)
2723
return self._response_to_configobj(response)
2725
def set_option(self, value, name, section=None):
2726
"""Set the value associated with a named option.
2728
:param value: The value to set
2729
:param name: The name of the value to set
2730
:param section: The section the option is in (if any)
2732
medium = self._branch._client._medium
2733
if medium._is_remote_before((1, 14)):
2734
return self._vfs_set_option(value, name, section)
2736
path = self._branch._remote_path()
2737
response = self._branch._client.call('Branch.set_config_option',
2738
path, self._branch._lock_token, self._branch._repo_lock_token,
2739
value.encode('utf8'), name, section or '')
2740
except errors.UnknownSmartMethod:
2741
medium._remember_remote_is_before((1, 14))
2742
return self._vfs_set_option(value, name, section)
2744
raise errors.UnexpectedSmartServerResponse(response)
2746
def _real_object(self):
2747
self._branch._ensure_real()
2748
return self._branch._real_branch
2750
def _vfs_set_option(self, value, name, section=None):
2751
return self._real_object()._get_config().set_option(
2752
value, name, section)
2755
class RemoteBzrDirConfig(RemoteConfig):
2756
"""A RemoteConfig for BzrDirs."""
2758
def __init__(self, bzrdir):
2759
self._bzrdir = bzrdir
2761
def _get_configobj(self):
2762
medium = self._bzrdir._client._medium
2763
verb = 'BzrDir.get_config_file'
2764
if medium._is_remote_before((1, 15)):
2765
raise errors.UnknownSmartMethod(verb)
2766
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2767
response = self._bzrdir._call_expecting_body(
2769
return self._response_to_configobj(response)
2771
def _vfs_get_option(self, name, section, default):
2772
return self._real_object()._get_config().get_option(
2773
name, section, default)
2775
def set_option(self, value, name, section=None):
2776
"""Set the value associated with a named option.
2778
:param value: The value to set
2779
:param name: The name of the value to set
2780
:param section: The section the option is in (if any)
2782
return self._real_object()._get_config().set_option(
2783
value, name, section)
2785
def _real_object(self):
2786
self._bzrdir._ensure_real()
2787
return self._bzrdir._real_bzrdir
2791
def _extract_tar(tar, to_dir):
2792
"""Extract all the contents of a tarfile object.
2794
A replacement for extractall, which is not present in python2.4
2797
tar.extract(tarinfo, to_dir)
2800
def _translate_error(err, **context):
2801
"""Translate an ErrorFromSmartServer into a more useful error.
2803
Possible context keys:
2811
If the error from the server doesn't match a known pattern, then
2812
UnknownErrorFromSmartServer is raised.
2816
return context[name]
2817
except KeyError, key_err:
2818
mutter('Missing key %r in context %r', key_err.args[0], context)
2821
"""Get the path from the context if present, otherwise use first error
2825
return context['path']
2826
except KeyError, key_err:
2828
return err.error_args[0]
2829
except IndexError, idx_err:
2831
'Missing key %r in context %r', key_err.args[0], context)
2834
if err.error_verb == 'IncompatibleRepositories':
2835
raise errors.IncompatibleRepositories(err.error_args[0],
2836
err.error_args[1], err.error_args[2])
2837
elif err.error_verb == 'NoSuchRevision':
2838
raise NoSuchRevision(find('branch'), err.error_args[0])
2839
elif err.error_verb == 'nosuchrevision':
2840
raise NoSuchRevision(find('repository'), err.error_args[0])
2841
elif err.error_verb == 'nobranch':
2842
if len(err.error_args) >= 1:
2843
extra = err.error_args[0]
2846
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2848
elif err.error_verb == 'norepository':
2849
raise errors.NoRepositoryPresent(find('bzrdir'))
2850
elif err.error_verb == 'LockContention':
2851
raise errors.LockContention('(remote lock)')
2852
elif err.error_verb == 'UnlockableTransport':
2853
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2854
elif err.error_verb == 'LockFailed':
2855
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2856
elif err.error_verb == 'TokenMismatch':
2857
raise errors.TokenMismatch(find('token'), '(remote token)')
2858
elif err.error_verb == 'Diverged':
2859
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2860
elif err.error_verb == 'TipChangeRejected':
2861
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2862
elif err.error_verb == 'UnstackableBranchFormat':
2863
raise errors.UnstackableBranchFormat(*err.error_args)
2864
elif err.error_verb == 'UnstackableRepositoryFormat':
2865
raise errors.UnstackableRepositoryFormat(*err.error_args)
2866
elif err.error_verb == 'NotStacked':
2867
raise errors.NotStacked(branch=find('branch'))
2868
elif err.error_verb == 'PermissionDenied':
2870
if len(err.error_args) >= 2:
2871
extra = err.error_args[1]
2874
raise errors.PermissionDenied(path, extra=extra)
2875
elif err.error_verb == 'ReadError':
2877
raise errors.ReadError(path)
2878
elif err.error_verb == 'NoSuchFile':
2880
raise errors.NoSuchFile(path)
2881
elif err.error_verb == 'FileExists':
2882
raise errors.FileExists(err.error_args[0])
2883
elif err.error_verb == 'DirectoryNotEmpty':
2884
raise errors.DirectoryNotEmpty(err.error_args[0])
2885
elif err.error_verb == 'ShortReadvError':
2886
args = err.error_args
2887
raise errors.ShortReadvError(
2888
args[0], int(args[1]), int(args[2]), int(args[3]))
2889
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2890
encoding = str(err.error_args[0]) # encoding must always be a string
2891
val = err.error_args[1]
2892
start = int(err.error_args[2])
2893
end = int(err.error_args[3])
2894
reason = str(err.error_args[4]) # reason must always be a string
2895
if val.startswith('u:'):
2896
val = val[2:].decode('utf-8')
2897
elif val.startswith('s:'):
2898
val = val[2:].decode('base64')
2899
if err.error_verb == 'UnicodeDecodeError':
2900
raise UnicodeDecodeError(encoding, val, start, end, reason)
2901
elif err.error_verb == 'UnicodeEncodeError':
2902
raise UnicodeEncodeError(encoding, val, start, end, reason)
2903
elif err.error_verb == 'ReadOnlyError':
2904
raise errors.TransportNotPossible('readonly transport')
2905
raise errors.UnknownErrorFromSmartServer(err)