1
# Copyright (C) 2006, 2007, 2008, 2009 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):
246
# as per meta1 formats - just delegate to the format object which may
248
real_branch = self._format.get_branch_format().initialize(self)
249
if not isinstance(real_branch, RemoteBranch):
250
result = RemoteBranch(self, self.find_repository(), real_branch)
253
# BzrDir.clone_on_transport() uses the result of create_branch but does
254
# not return it to its callers; we save approximately 8% of our round
255
# trips by handing the branch we created back to the first caller to
256
# open_branch rather than probing anew. Long term we need a API in
257
# bzrdir that doesn't discard result objects (like result_branch).
259
self._next_open_branch_result = result
262
def destroy_branch(self):
263
"""See BzrDir.destroy_branch"""
265
self._real_bzrdir.destroy_branch()
266
self._next_open_branch_result = None
268
def create_workingtree(self, revision_id=None, from_branch=None):
269
raise errors.NotLocalUrl(self.transport.base)
271
def find_branch_format(self):
272
"""Find the branch 'format' for this bzrdir.
274
This might be a synthetic object for e.g. RemoteBranch and SVN.
276
b = self.open_branch()
279
def get_branch_reference(self):
280
"""See BzrDir.get_branch_reference()."""
281
response = self._get_branch_reference()
282
if response[0] == 'ref':
287
def _get_branch_reference(self):
288
path = self._path_for_remote_call(self._client)
289
medium = self._client._medium
290
if not medium._is_remote_before((1, 13)):
292
response = self._call('BzrDir.open_branchV2', path)
293
if response[0] not in ('ref', 'branch'):
294
raise errors.UnexpectedSmartServerResponse(response)
296
except errors.UnknownSmartMethod:
297
medium._remember_remote_is_before((1, 13))
298
response = self._call('BzrDir.open_branch', path)
299
if response[0] != 'ok':
300
raise errors.UnexpectedSmartServerResponse(response)
301
if response[1] != '':
302
return ('ref', response[1])
304
return ('branch', '')
306
def _get_tree_branch(self):
307
"""See BzrDir._get_tree_branch()."""
308
return None, self.open_branch()
310
def open_branch(self, _unsupported=False, ignore_fallbacks=False):
312
raise NotImplementedError('unsupported flag support not implemented yet.')
313
if self._next_open_branch_result is not None:
314
# See create_branch for details.
315
result = self._next_open_branch_result
316
self._next_open_branch_result = None
318
response = self._get_branch_reference()
319
if response[0] == 'ref':
320
# a branch reference, use the existing BranchReference logic.
321
format = BranchReferenceFormat()
322
return format.open(self, _found=True, location=response[1],
323
ignore_fallbacks=ignore_fallbacks)
324
branch_format_name = response[1]
325
if not branch_format_name:
326
branch_format_name = None
327
format = RemoteBranchFormat(network_name=branch_format_name)
328
return RemoteBranch(self, self.find_repository(), format=format,
329
setup_stacking=not ignore_fallbacks)
331
def _open_repo_v1(self, path):
332
verb = 'BzrDir.find_repository'
333
response = self._call(verb, path)
334
if response[0] != 'ok':
335
raise errors.UnexpectedSmartServerResponse(response)
336
# servers that only support the v1 method don't support external
339
repo = self._real_bzrdir.open_repository()
340
response = response + ('no', repo._format.network_name())
341
return response, repo
343
def _open_repo_v2(self, path):
344
verb = 'BzrDir.find_repositoryV2'
345
response = self._call(verb, path)
346
if response[0] != 'ok':
347
raise errors.UnexpectedSmartServerResponse(response)
349
repo = self._real_bzrdir.open_repository()
350
response = response + (repo._format.network_name(),)
351
return response, repo
353
def _open_repo_v3(self, path):
354
verb = 'BzrDir.find_repositoryV3'
355
medium = self._client._medium
356
if medium._is_remote_before((1, 13)):
357
raise errors.UnknownSmartMethod(verb)
359
response = self._call(verb, path)
360
except errors.UnknownSmartMethod:
361
medium._remember_remote_is_before((1, 13))
363
if response[0] != 'ok':
364
raise errors.UnexpectedSmartServerResponse(response)
365
return response, None
367
def open_repository(self):
368
path = self._path_for_remote_call(self._client)
370
for probe in [self._open_repo_v3, self._open_repo_v2,
373
response, real_repo = probe(path)
375
except errors.UnknownSmartMethod:
378
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
379
if response[0] != 'ok':
380
raise errors.UnexpectedSmartServerResponse(response)
381
if len(response) != 6:
382
raise SmartProtocolError('incorrect response length %s' % (response,))
383
if response[1] == '':
384
# repo is at this dir.
385
format = response_tuple_to_repo_format(response[2:])
386
# Used to support creating a real format instance when needed.
387
format._creating_bzrdir = self
388
remote_repo = RemoteRepository(self, format)
389
format._creating_repo = remote_repo
390
if real_repo is not None:
391
remote_repo._set_real_repository(real_repo)
394
raise errors.NoRepositoryPresent(self)
396
def has_workingtree(self):
397
if self._has_working_tree is None:
399
self._has_working_tree = self._real_bzrdir.has_workingtree()
400
return self._has_working_tree
402
def open_workingtree(self, recommend_upgrade=True):
403
if self.has_workingtree():
404
raise errors.NotLocalUrl(self.root_transport)
406
raise errors.NoWorkingTree(self.root_transport.base)
408
def _path_for_remote_call(self, client):
409
"""Return the path to be used for this bzrdir in a remote call."""
410
return client.remote_path_from_transport(self.root_transport)
412
def get_branch_transport(self, branch_format):
414
return self._real_bzrdir.get_branch_transport(branch_format)
416
def get_repository_transport(self, repository_format):
418
return self._real_bzrdir.get_repository_transport(repository_format)
420
def get_workingtree_transport(self, workingtree_format):
422
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
424
def can_convert_format(self):
425
"""Upgrading of remote bzrdirs is not supported yet."""
428
def needs_format_conversion(self, format=None):
429
"""Upgrading of remote bzrdirs is not supported yet."""
431
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
432
% 'needs_format_conversion(format=None)')
435
def clone(self, url, revision_id=None, force_new_repo=False,
436
preserve_stacking=False):
438
return self._real_bzrdir.clone(url, revision_id=revision_id,
439
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
441
def _get_config(self):
442
return RemoteBzrDirConfig(self)
445
class RemoteRepositoryFormat(repository.RepositoryFormat):
446
"""Format for repositories accessed over a _SmartClient.
448
Instances of this repository are represented by RemoteRepository
451
The RemoteRepositoryFormat is parameterized during construction
452
to reflect the capabilities of the real, remote format. Specifically
453
the attributes rich_root_data and supports_tree_reference are set
454
on a per instance basis, and are not set (and should not be) at
457
:ivar _custom_format: If set, a specific concrete repository format that
458
will be used when initializing a repository with this
459
RemoteRepositoryFormat.
460
:ivar _creating_repo: If set, the repository object that this
461
RemoteRepositoryFormat was created for: it can be called into
462
to obtain data like the network name.
465
_matchingbzrdir = RemoteBzrDirFormat()
468
repository.RepositoryFormat.__init__(self)
469
self._custom_format = None
470
self._network_name = None
471
self._creating_bzrdir = None
472
self._supports_chks = None
473
self._supports_external_lookups = None
474
self._supports_tree_reference = None
475
self._rich_root_data = None
478
return "%s(_network_name=%r)" % (self.__class__.__name__,
482
def fast_deltas(self):
484
return self._custom_format.fast_deltas
487
def rich_root_data(self):
488
if self._rich_root_data is None:
490
self._rich_root_data = self._custom_format.rich_root_data
491
return self._rich_root_data
494
def supports_chks(self):
495
if self._supports_chks is None:
497
self._supports_chks = self._custom_format.supports_chks
498
return self._supports_chks
501
def supports_external_lookups(self):
502
if self._supports_external_lookups is None:
504
self._supports_external_lookups = \
505
self._custom_format.supports_external_lookups
506
return self._supports_external_lookups
509
def supports_tree_reference(self):
510
if self._supports_tree_reference is None:
512
self._supports_tree_reference = \
513
self._custom_format.supports_tree_reference
514
return self._supports_tree_reference
516
def _vfs_initialize(self, a_bzrdir, shared):
517
"""Helper for common code in initialize."""
518
if self._custom_format:
519
# Custom format requested
520
result = self._custom_format.initialize(a_bzrdir, shared=shared)
521
elif self._creating_bzrdir is not None:
522
# Use the format that the repository we were created to back
524
prior_repo = self._creating_bzrdir.open_repository()
525
prior_repo._ensure_real()
526
result = prior_repo._real_repository._format.initialize(
527
a_bzrdir, shared=shared)
529
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
530
# support remote initialization.
531
# We delegate to a real object at this point (as RemoteBzrDir
532
# delegate to the repository format which would lead to infinite
533
# recursion if we just called a_bzrdir.create_repository.
534
a_bzrdir._ensure_real()
535
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
536
if not isinstance(result, RemoteRepository):
537
return self.open(a_bzrdir)
541
def initialize(self, a_bzrdir, shared=False):
542
# Being asked to create on a non RemoteBzrDir:
543
if not isinstance(a_bzrdir, RemoteBzrDir):
544
return self._vfs_initialize(a_bzrdir, shared)
545
medium = a_bzrdir._client._medium
546
if medium._is_remote_before((1, 13)):
547
return self._vfs_initialize(a_bzrdir, shared)
548
# Creating on a remote bzr dir.
549
# 1) get the network name to use.
550
if self._custom_format:
551
network_name = self._custom_format.network_name()
552
elif self._network_name:
553
network_name = self._network_name
555
# Select the current bzrlib default and ask for that.
556
reference_bzrdir_format = bzrdir.format_registry.get('default')()
557
reference_format = reference_bzrdir_format.repository_format
558
network_name = reference_format.network_name()
559
# 2) try direct creation via RPC
560
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
561
verb = 'BzrDir.create_repository'
567
response = a_bzrdir._call(verb, path, network_name, shared_str)
568
except errors.UnknownSmartMethod:
569
# Fallback - use vfs methods
570
medium._remember_remote_is_before((1, 13))
571
return self._vfs_initialize(a_bzrdir, shared)
573
# Turn the response into a RemoteRepository object.
574
format = response_tuple_to_repo_format(response[1:])
575
# Used to support creating a real format instance when needed.
576
format._creating_bzrdir = a_bzrdir
577
remote_repo = RemoteRepository(a_bzrdir, format)
578
format._creating_repo = remote_repo
581
def open(self, a_bzrdir):
582
if not isinstance(a_bzrdir, RemoteBzrDir):
583
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
584
return a_bzrdir.open_repository()
586
def _ensure_real(self):
587
if self._custom_format is None:
588
self._custom_format = repository.network_format_registry.get(
592
def _fetch_order(self):
594
return self._custom_format._fetch_order
597
def _fetch_uses_deltas(self):
599
return self._custom_format._fetch_uses_deltas
602
def _fetch_reconcile(self):
604
return self._custom_format._fetch_reconcile
606
def get_format_description(self):
608
return 'Remote: ' + self._custom_format.get_format_description()
610
def __eq__(self, other):
611
return self.__class__ is other.__class__
613
def network_name(self):
614
if self._network_name:
615
return self._network_name
616
self._creating_repo._ensure_real()
617
return self._creating_repo._real_repository._format.network_name()
620
def pack_compresses(self):
622
return self._custom_format.pack_compresses
625
def _serializer(self):
627
return self._custom_format._serializer
630
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
631
"""Repository accessed over rpc.
633
For the moment most operations are performed using local transport-backed
637
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
638
"""Create a RemoteRepository instance.
640
:param remote_bzrdir: The bzrdir hosting this repository.
641
:param format: The RemoteFormat object to use.
642
:param real_repository: If not None, a local implementation of the
643
repository logic for the repository, usually accessing the data
645
:param _client: Private testing parameter - override the smart client
646
to be used by the repository.
649
self._real_repository = real_repository
651
self._real_repository = None
652
self.bzrdir = remote_bzrdir
654
self._client = remote_bzrdir._client
656
self._client = _client
657
self._format = format
658
self._lock_mode = None
659
self._lock_token = None
661
self._leave_lock = False
662
# Cache of revision parents; misses are cached during read locks, and
663
# write locks when no _real_repository has been set.
664
self._unstacked_provider = graph.CachingParentsProvider(
665
get_parent_map=self._get_parent_map_rpc)
666
self._unstacked_provider.disable_cache()
668
# These depend on the actual remote format, so force them off for
669
# maximum compatibility. XXX: In future these should depend on the
670
# remote repository instance, but this is irrelevant until we perform
671
# reconcile via an RPC call.
672
self._reconcile_does_inventory_gc = False
673
self._reconcile_fixes_text_parents = False
674
self._reconcile_backsup_inventory = False
675
self.base = self.bzrdir.transport.base
676
# Additional places to query for data.
677
self._fallback_repositories = []
680
return "%s(%s)" % (self.__class__.__name__, self.base)
684
def abort_write_group(self, suppress_errors=False):
685
"""Complete a write group on the decorated repository.
687
Smart methods perform operations in a single step so this API
688
is not really applicable except as a compatibility thunk
689
for older plugins that don't use e.g. the CommitBuilder
692
:param suppress_errors: see Repository.abort_write_group.
695
return self._real_repository.abort_write_group(
696
suppress_errors=suppress_errors)
700
"""Decorate the real repository for now.
702
In the long term a full blown network facility is needed to avoid
703
creating a real repository object locally.
706
return self._real_repository.chk_bytes
708
def commit_write_group(self):
709
"""Complete a write group on the decorated repository.
711
Smart methods perform operations in a single step so this API
712
is not really applicable except as a compatibility thunk
713
for older plugins that don't use e.g. the CommitBuilder
717
return self._real_repository.commit_write_group()
719
def resume_write_group(self, tokens):
721
return self._real_repository.resume_write_group(tokens)
723
def suspend_write_group(self):
725
return self._real_repository.suspend_write_group()
727
def get_missing_parent_inventories(self, check_for_missing_texts=True):
729
return self._real_repository.get_missing_parent_inventories(
730
check_for_missing_texts=check_for_missing_texts)
732
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
734
return self._real_repository.get_rev_id_for_revno(
737
def get_rev_id_for_revno(self, revno, known_pair):
738
"""See Repository.get_rev_id_for_revno."""
739
path = self.bzrdir._path_for_remote_call(self._client)
741
if self._client._medium._is_remote_before((1, 17)):
742
return self._get_rev_id_for_revno_vfs(revno, known_pair)
743
response = self._call(
744
'Repository.get_rev_id_for_revno', path, revno, known_pair)
745
except errors.UnknownSmartMethod:
746
self._client._medium._remember_remote_is_before((1, 17))
747
return self._get_rev_id_for_revno_vfs(revno, known_pair)
748
if response[0] == 'ok':
749
return True, response[1]
750
elif response[0] == 'history-incomplete':
751
known_pair = response[1:3]
752
for fallback in self._fallback_repositories:
753
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
758
# Not found in any fallbacks
759
return False, known_pair
761
raise errors.UnexpectedSmartServerResponse(response)
763
def _ensure_real(self):
764
"""Ensure that there is a _real_repository set.
766
Used before calls to self._real_repository.
768
Note that _ensure_real causes many roundtrips to the server which are
769
not desirable, and prevents the use of smart one-roundtrip RPC's to
770
perform complex operations (such as accessing parent data, streaming
771
revisions etc). Adding calls to _ensure_real should only be done when
772
bringing up new functionality, adding fallbacks for smart methods that
773
require a fallback path, and never to replace an existing smart method
774
invocation. If in doubt chat to the bzr network team.
776
if self._real_repository is None:
777
if 'hpssvfs' in debug.debug_flags:
779
warning('VFS Repository access triggered\n%s',
780
''.join(traceback.format_stack()))
781
self._unstacked_provider.missing_keys.clear()
782
self.bzrdir._ensure_real()
783
self._set_real_repository(
784
self.bzrdir._real_bzrdir.open_repository())
786
def _translate_error(self, err, **context):
787
self.bzrdir._translate_error(err, repository=self, **context)
789
def find_text_key_references(self):
790
"""Find the text key references within the repository.
792
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
793
revision_ids. Each altered file-ids has the exact revision_ids that
794
altered it listed explicitly.
795
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
796
to whether they were referred to by the inventory of the
797
revision_id that they contain. The inventory texts from all present
798
revision ids are assessed to generate this report.
801
return self._real_repository.find_text_key_references()
803
def _generate_text_key_index(self):
804
"""Generate a new text key index for the repository.
806
This is an expensive function that will take considerable time to run.
808
:return: A dict mapping (file_id, revision_id) tuples to a list of
809
parents, also (file_id, revision_id) tuples.
812
return self._real_repository._generate_text_key_index()
814
def _get_revision_graph(self, revision_id):
815
"""Private method for using with old (< 1.2) servers to fallback."""
816
if revision_id is None:
818
elif revision.is_null(revision_id):
821
path = self.bzrdir._path_for_remote_call(self._client)
822
response = self._call_expecting_body(
823
'Repository.get_revision_graph', path, revision_id)
824
response_tuple, response_handler = response
825
if response_tuple[0] != 'ok':
826
raise errors.UnexpectedSmartServerResponse(response_tuple)
827
coded = response_handler.read_body_bytes()
829
# no revisions in this repository!
831
lines = coded.split('\n')
834
d = tuple(line.split())
835
revision_graph[d[0]] = d[1:]
837
return revision_graph
840
"""See Repository._get_sink()."""
841
return RemoteStreamSink(self)
843
def _get_source(self, to_format):
844
"""Return a source for streaming from this repository."""
845
return RemoteStreamSource(self, to_format)
848
def has_revision(self, revision_id):
849
"""True if this repository has a copy of the revision."""
850
# Copy of bzrlib.repository.Repository.has_revision
851
return revision_id in self.has_revisions((revision_id,))
854
def has_revisions(self, revision_ids):
855
"""Probe to find out the presence of multiple revisions.
857
:param revision_ids: An iterable of revision_ids.
858
:return: A set of the revision_ids that were present.
860
# Copy of bzrlib.repository.Repository.has_revisions
861
parent_map = self.get_parent_map(revision_ids)
862
result = set(parent_map)
863
if _mod_revision.NULL_REVISION in revision_ids:
864
result.add(_mod_revision.NULL_REVISION)
867
def _has_same_fallbacks(self, other_repo):
868
"""Returns true if the repositories have the same fallbacks."""
869
# XXX: copied from Repository; it should be unified into a base class
870
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
871
my_fb = self._fallback_repositories
872
other_fb = other_repo._fallback_repositories
873
if len(my_fb) != len(other_fb):
875
for f, g in zip(my_fb, other_fb):
876
if not f.has_same_location(g):
880
def has_same_location(self, other):
881
# TODO: Move to RepositoryBase and unify with the regular Repository
882
# one; unfortunately the tests rely on slightly different behaviour at
883
# present -- mbp 20090710
884
return (self.__class__ is other.__class__ and
885
self.bzrdir.transport.base == other.bzrdir.transport.base)
887
def get_graph(self, other_repository=None):
888
"""Return the graph for this repository format"""
889
parents_provider = self._make_parents_provider(other_repository)
890
return graph.Graph(parents_provider)
892
def gather_stats(self, revid=None, committers=None):
893
"""See Repository.gather_stats()."""
894
path = self.bzrdir._path_for_remote_call(self._client)
895
# revid can be None to indicate no revisions, not just NULL_REVISION
896
if revid is None or revision.is_null(revid):
900
if committers is None or not committers:
901
fmt_committers = 'no'
903
fmt_committers = 'yes'
904
response_tuple, response_handler = self._call_expecting_body(
905
'Repository.gather_stats', path, fmt_revid, fmt_committers)
906
if response_tuple[0] != 'ok':
907
raise errors.UnexpectedSmartServerResponse(response_tuple)
909
body = response_handler.read_body_bytes()
911
for line in body.split('\n'):
914
key, val_text = line.split(':')
915
if key in ('revisions', 'size', 'committers'):
916
result[key] = int(val_text)
917
elif key in ('firstrev', 'latestrev'):
918
values = val_text.split(' ')[1:]
919
result[key] = (float(values[0]), long(values[1]))
923
def find_branches(self, using=False):
924
"""See Repository.find_branches()."""
925
# should be an API call to the server.
927
return self._real_repository.find_branches(using=using)
929
def get_physical_lock_status(self):
930
"""See Repository.get_physical_lock_status()."""
931
# should be an API call to the server.
933
return self._real_repository.get_physical_lock_status()
935
def is_in_write_group(self):
936
"""Return True if there is an open write group.
938
write groups are only applicable locally for the smart server..
940
if self._real_repository:
941
return self._real_repository.is_in_write_group()
944
return self._lock_count >= 1
947
"""See Repository.is_shared()."""
948
path = self.bzrdir._path_for_remote_call(self._client)
949
response = self._call('Repository.is_shared', path)
950
if response[0] not in ('yes', 'no'):
951
raise SmartProtocolError('unexpected response code %s' % (response,))
952
return response[0] == 'yes'
954
def is_write_locked(self):
955
return self._lock_mode == 'w'
957
def _warn_if_deprecated(self, branch=None):
958
# If we have a real repository, the check will be done there, if we
959
# don't the check will be done remotely.
963
# wrong eventually - want a local lock cache context
964
if not self._lock_mode:
966
self._lock_mode = 'r'
968
self._unstacked_provider.enable_cache(cache_misses=True)
969
if self._real_repository is not None:
970
self._real_repository.lock_read()
971
for repo in self._fallback_repositories:
974
self._lock_count += 1
976
def _remote_lock_write(self, token):
977
path = self.bzrdir._path_for_remote_call(self._client)
980
err_context = {'token': token}
981
response = self._call('Repository.lock_write', path, token,
983
if response[0] == 'ok':
987
raise errors.UnexpectedSmartServerResponse(response)
989
def lock_write(self, token=None, _skip_rpc=False):
990
if not self._lock_mode:
993
if self._lock_token is not None:
994
if token != self._lock_token:
995
raise errors.TokenMismatch(token, self._lock_token)
996
self._lock_token = token
998
self._lock_token = self._remote_lock_write(token)
999
# if self._lock_token is None, then this is something like packs or
1000
# svn where we don't get to lock the repo, or a weave style repository
1001
# where we cannot lock it over the wire and attempts to do so will
1003
if self._real_repository is not None:
1004
self._real_repository.lock_write(token=self._lock_token)
1005
if token is not None:
1006
self._leave_lock = True
1008
self._leave_lock = False
1009
self._lock_mode = 'w'
1010
self._lock_count = 1
1011
cache_misses = self._real_repository is None
1012
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1013
for repo in self._fallback_repositories:
1014
# Writes don't affect fallback repos
1016
elif self._lock_mode == 'r':
1017
raise errors.ReadOnlyError(self)
1019
self._lock_count += 1
1020
return self._lock_token or None
1022
def leave_lock_in_place(self):
1023
if not self._lock_token:
1024
raise NotImplementedError(self.leave_lock_in_place)
1025
self._leave_lock = True
1027
def dont_leave_lock_in_place(self):
1028
if not self._lock_token:
1029
raise NotImplementedError(self.dont_leave_lock_in_place)
1030
self._leave_lock = False
1032
def _set_real_repository(self, repository):
1033
"""Set the _real_repository for this repository.
1035
:param repository: The repository to fallback to for non-hpss
1036
implemented operations.
1038
if self._real_repository is not None:
1039
# Replacing an already set real repository.
1040
# We cannot do this [currently] if the repository is locked -
1041
# synchronised state might be lost.
1042
if self.is_locked():
1043
raise AssertionError('_real_repository is already set')
1044
if isinstance(repository, RemoteRepository):
1045
raise AssertionError()
1046
self._real_repository = repository
1047
# three code paths happen here:
1048
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1049
# up stacking. In this case self._fallback_repositories is [], and the
1050
# real repo is already setup. Preserve the real repo and
1051
# RemoteRepository.add_fallback_repository will avoid adding
1053
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1054
# ensure_real is triggered from a branch, the real repository to
1055
# set already has a matching list with separate instances, but
1056
# as they are also RemoteRepositories we don't worry about making the
1057
# lists be identical.
1058
# 3) new servers, RemoteRepository.ensure_real is triggered before
1059
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1060
# and need to populate it.
1061
if (self._fallback_repositories and
1062
len(self._real_repository._fallback_repositories) !=
1063
len(self._fallback_repositories)):
1064
if len(self._real_repository._fallback_repositories):
1065
raise AssertionError(
1066
"cannot cleanly remove existing _fallback_repositories")
1067
for fb in self._fallback_repositories:
1068
self._real_repository.add_fallback_repository(fb)
1069
if self._lock_mode == 'w':
1070
# if we are already locked, the real repository must be able to
1071
# acquire the lock with our token.
1072
self._real_repository.lock_write(self._lock_token)
1073
elif self._lock_mode == 'r':
1074
self._real_repository.lock_read()
1076
def start_write_group(self):
1077
"""Start a write group on the decorated repository.
1079
Smart methods perform operations in a single step so this API
1080
is not really applicable except as a compatibility thunk
1081
for older plugins that don't use e.g. the CommitBuilder
1085
return self._real_repository.start_write_group()
1087
def _unlock(self, token):
1088
path = self.bzrdir._path_for_remote_call(self._client)
1090
# with no token the remote repository is not persistently locked.
1092
err_context = {'token': token}
1093
response = self._call('Repository.unlock', path, token,
1095
if response == ('ok',):
1098
raise errors.UnexpectedSmartServerResponse(response)
1100
@only_raises(errors.LockNotHeld, errors.LockBroken)
1102
if not self._lock_count:
1103
return lock.cant_unlock_not_held(self)
1104
self._lock_count -= 1
1105
if self._lock_count > 0:
1107
self._unstacked_provider.disable_cache()
1108
old_mode = self._lock_mode
1109
self._lock_mode = None
1111
# The real repository is responsible at present for raising an
1112
# exception if it's in an unfinished write group. However, it
1113
# normally will *not* actually remove the lock from disk - that's
1114
# done by the server on receiving the Repository.unlock call.
1115
# This is just to let the _real_repository stay up to date.
1116
if self._real_repository is not None:
1117
self._real_repository.unlock()
1119
# The rpc-level lock should be released even if there was a
1120
# problem releasing the vfs-based lock.
1122
# Only write-locked repositories need to make a remote method
1123
# call to perform the unlock.
1124
old_token = self._lock_token
1125
self._lock_token = None
1126
if not self._leave_lock:
1127
self._unlock(old_token)
1128
# Fallbacks are always 'lock_read()' so we don't pay attention to
1130
for repo in self._fallback_repositories:
1133
def break_lock(self):
1134
# should hand off to the network
1136
return self._real_repository.break_lock()
1138
def _get_tarball(self, compression):
1139
"""Return a TemporaryFile containing a repository tarball.
1141
Returns None if the server does not support sending tarballs.
1144
path = self.bzrdir._path_for_remote_call(self._client)
1146
response, protocol = self._call_expecting_body(
1147
'Repository.tarball', path, compression)
1148
except errors.UnknownSmartMethod:
1149
protocol.cancel_read_body()
1151
if response[0] == 'ok':
1152
# Extract the tarball and return it
1153
t = tempfile.NamedTemporaryFile()
1154
# TODO: rpc layer should read directly into it...
1155
t.write(protocol.read_body_bytes())
1158
raise errors.UnexpectedSmartServerResponse(response)
1160
def sprout(self, to_bzrdir, revision_id=None):
1161
# TODO: Option to control what format is created?
1163
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1165
dest_repo.fetch(self, revision_id=revision_id)
1168
### These methods are just thin shims to the VFS object for now.
1170
def revision_tree(self, revision_id):
1172
return self._real_repository.revision_tree(revision_id)
1174
def get_serializer_format(self):
1176
return self._real_repository.get_serializer_format()
1178
def get_commit_builder(self, branch, parents, config, timestamp=None,
1179
timezone=None, committer=None, revprops=None,
1181
# FIXME: It ought to be possible to call this without immediately
1182
# triggering _ensure_real. For now it's the easiest thing to do.
1184
real_repo = self._real_repository
1185
builder = real_repo.get_commit_builder(branch, parents,
1186
config, timestamp=timestamp, timezone=timezone,
1187
committer=committer, revprops=revprops, revision_id=revision_id)
1190
def add_fallback_repository(self, repository):
1191
"""Add a repository to use for looking up data not held locally.
1193
:param repository: A repository.
1195
if not self._format.supports_external_lookups:
1196
raise errors.UnstackableRepositoryFormat(
1197
self._format.network_name(), self.base)
1198
# We need to accumulate additional repositories here, to pass them in
1201
if self.is_locked():
1202
# We will call fallback.unlock() when we transition to the unlocked
1203
# state, so always add a lock here. If a caller passes us a locked
1204
# repository, they are responsible for unlocking it later.
1205
repository.lock_read()
1206
self._fallback_repositories.append(repository)
1207
# If self._real_repository was parameterised already (e.g. because a
1208
# _real_branch had its get_stacked_on_url method called), then the
1209
# repository to be added may already be in the _real_repositories list.
1210
if self._real_repository is not None:
1211
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1212
self._real_repository._fallback_repositories]
1213
if repository.bzrdir.root_transport.base not in fallback_locations:
1214
self._real_repository.add_fallback_repository(repository)
1216
def add_inventory(self, revid, inv, parents):
1218
return self._real_repository.add_inventory(revid, inv, parents)
1220
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1223
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1224
delta, new_revision_id, parents)
1226
def add_revision(self, rev_id, rev, inv=None, config=None):
1228
return self._real_repository.add_revision(
1229
rev_id, rev, inv=inv, config=config)
1232
def get_inventory(self, revision_id):
1234
return self._real_repository.get_inventory(revision_id)
1236
def iter_inventories(self, revision_ids, ordering=None):
1238
return self._real_repository.iter_inventories(revision_ids, ordering)
1241
def get_revision(self, revision_id):
1243
return self._real_repository.get_revision(revision_id)
1245
def get_transaction(self):
1247
return self._real_repository.get_transaction()
1250
def clone(self, a_bzrdir, revision_id=None):
1252
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1254
def make_working_trees(self):
1255
"""See Repository.make_working_trees"""
1257
return self._real_repository.make_working_trees()
1259
def refresh_data(self):
1260
"""Re-read any data needed to to synchronise with disk.
1262
This method is intended to be called after another repository instance
1263
(such as one used by a smart server) has inserted data into the
1264
repository. It may not be called during a write group, but may be
1265
called at any other time.
1267
if self.is_in_write_group():
1268
raise errors.InternalBzrError(
1269
"May not refresh_data while in a write group.")
1270
if self._real_repository is not None:
1271
self._real_repository.refresh_data()
1273
def revision_ids_to_search_result(self, result_set):
1274
"""Convert a set of revision ids to a graph SearchResult."""
1275
result_parents = set()
1276
for parents in self.get_graph().get_parent_map(
1277
result_set).itervalues():
1278
result_parents.update(parents)
1279
included_keys = result_set.intersection(result_parents)
1280
start_keys = result_set.difference(included_keys)
1281
exclude_keys = result_parents.difference(result_set)
1282
result = graph.SearchResult(start_keys, exclude_keys,
1283
len(result_set), result_set)
1287
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1288
"""Return the revision ids that other has that this does not.
1290
These are returned in topological order.
1292
revision_id: only return revision ids included by revision_id.
1294
return repository.InterRepository.get(
1295
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1297
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1299
# No base implementation to use as RemoteRepository is not a subclass
1300
# of Repository; so this is a copy of Repository.fetch().
1301
if fetch_spec is not None and revision_id is not None:
1302
raise AssertionError(
1303
"fetch_spec and revision_id are mutually exclusive.")
1304
if self.is_in_write_group():
1305
raise errors.InternalBzrError(
1306
"May not fetch while in a write group.")
1307
# fast path same-url fetch operations
1308
if (self.has_same_location(source)
1309
and fetch_spec is None
1310
and self._has_same_fallbacks(source)):
1311
# check that last_revision is in 'from' and then return a
1313
if (revision_id is not None and
1314
not revision.is_null(revision_id)):
1315
self.get_revision(revision_id)
1317
# if there is no specific appropriate InterRepository, this will get
1318
# the InterRepository base class, which raises an
1319
# IncompatibleRepositories when asked to fetch.
1320
inter = repository.InterRepository.get(source, self)
1321
return inter.fetch(revision_id=revision_id, pb=pb,
1322
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1324
def create_bundle(self, target, base, fileobj, format=None):
1326
self._real_repository.create_bundle(target, base, fileobj, format)
1329
def get_ancestry(self, revision_id, topo_sorted=True):
1331
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1333
def fileids_altered_by_revision_ids(self, revision_ids):
1335
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1337
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1339
return self._real_repository._get_versioned_file_checker(
1340
revisions, revision_versions_cache)
1342
def iter_files_bytes(self, desired_files):
1343
"""See Repository.iter_file_bytes.
1346
return self._real_repository.iter_files_bytes(desired_files)
1348
def get_parent_map(self, revision_ids):
1349
"""See bzrlib.Graph.get_parent_map()."""
1350
return self._make_parents_provider().get_parent_map(revision_ids)
1352
def _get_parent_map_rpc(self, keys):
1353
"""Helper for get_parent_map that performs the RPC."""
1354
medium = self._client._medium
1355
if medium._is_remote_before((1, 2)):
1356
# We already found out that the server can't understand
1357
# Repository.get_parent_map requests, so just fetch the whole
1360
# Note that this reads the whole graph, when only some keys are
1361
# wanted. On this old server there's no way (?) to get them all
1362
# in one go, and the user probably will have seen a warning about
1363
# the server being old anyhow.
1364
rg = self._get_revision_graph(None)
1365
# There is an API discrepancy between get_parent_map and
1366
# get_revision_graph. Specifically, a "key:()" pair in
1367
# get_revision_graph just means a node has no parents. For
1368
# "get_parent_map" it means the node is a ghost. So fix up the
1369
# graph to correct this.
1370
# https://bugs.launchpad.net/bzr/+bug/214894
1371
# There is one other "bug" which is that ghosts in
1372
# get_revision_graph() are not returned at all. But we won't worry
1373
# about that for now.
1374
for node_id, parent_ids in rg.iteritems():
1375
if parent_ids == ():
1376
rg[node_id] = (NULL_REVISION,)
1377
rg[NULL_REVISION] = ()
1382
raise ValueError('get_parent_map(None) is not valid')
1383
if NULL_REVISION in keys:
1384
keys.discard(NULL_REVISION)
1385
found_parents = {NULL_REVISION:()}
1387
return found_parents
1390
# TODO(Needs analysis): We could assume that the keys being requested
1391
# from get_parent_map are in a breadth first search, so typically they
1392
# will all be depth N from some common parent, and we don't have to
1393
# have the server iterate from the root parent, but rather from the
1394
# keys we're searching; and just tell the server the keyspace we
1395
# already have; but this may be more traffic again.
1397
# Transform self._parents_map into a search request recipe.
1398
# TODO: Manage this incrementally to avoid covering the same path
1399
# repeatedly. (The server will have to on each request, but the less
1400
# work done the better).
1402
# Negative caching notes:
1403
# new server sends missing when a request including the revid
1404
# 'include-missing:' is present in the request.
1405
# missing keys are serialised as missing:X, and we then call
1406
# provider.note_missing(X) for-all X
1407
parents_map = self._unstacked_provider.get_cached_map()
1408
if parents_map is None:
1409
# Repository is not locked, so there's no cache.
1411
# start_set is all the keys in the cache
1412
start_set = set(parents_map)
1413
# result set is all the references to keys in the cache
1414
result_parents = set()
1415
for parents in parents_map.itervalues():
1416
result_parents.update(parents)
1417
stop_keys = result_parents.difference(start_set)
1418
# We don't need to send ghosts back to the server as a position to
1420
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1421
key_count = len(parents_map)
1422
if (NULL_REVISION in result_parents
1423
and NULL_REVISION in self._unstacked_provider.missing_keys):
1424
# If we pruned NULL_REVISION from the stop_keys because it's also
1425
# in our cache of "missing" keys we need to increment our key count
1426
# by 1, because the reconsitituted SearchResult on the server will
1427
# still consider NULL_REVISION to be an included key.
1429
included_keys = start_set.intersection(result_parents)
1430
start_set.difference_update(included_keys)
1431
recipe = ('manual', start_set, stop_keys, key_count)
1432
body = self._serialise_search_recipe(recipe)
1433
path = self.bzrdir._path_for_remote_call(self._client)
1435
if type(key) is not str:
1437
"key %r not a plain string" % (key,))
1438
verb = 'Repository.get_parent_map'
1439
args = (path, 'include-missing:') + tuple(keys)
1441
response = self._call_with_body_bytes_expecting_body(
1443
except errors.UnknownSmartMethod:
1444
# Server does not support this method, so get the whole graph.
1445
# Worse, we have to force a disconnection, because the server now
1446
# doesn't realise it has a body on the wire to consume, so the
1447
# only way to recover is to abandon the connection.
1449
'Server is too old for fast get_parent_map, reconnecting. '
1450
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1452
# To avoid having to disconnect repeatedly, we keep track of the
1453
# fact the server doesn't understand remote methods added in 1.2.
1454
medium._remember_remote_is_before((1, 2))
1455
# Recurse just once and we should use the fallback code.
1456
return self._get_parent_map_rpc(keys)
1457
response_tuple, response_handler = response
1458
if response_tuple[0] not in ['ok']:
1459
response_handler.cancel_read_body()
1460
raise errors.UnexpectedSmartServerResponse(response_tuple)
1461
if response_tuple[0] == 'ok':
1462
coded = bz2.decompress(response_handler.read_body_bytes())
1464
# no revisions found
1466
lines = coded.split('\n')
1469
d = tuple(line.split())
1471
revision_graph[d[0]] = d[1:]
1474
if d[0].startswith('missing:'):
1476
self._unstacked_provider.note_missing_key(revid)
1478
# no parents - so give the Graph result
1480
revision_graph[d[0]] = (NULL_REVISION,)
1481
return revision_graph
1484
def get_signature_text(self, revision_id):
1486
return self._real_repository.get_signature_text(revision_id)
1489
def get_inventory_xml(self, revision_id):
1491
return self._real_repository.get_inventory_xml(revision_id)
1493
def deserialise_inventory(self, revision_id, xml):
1495
return self._real_repository.deserialise_inventory(revision_id, xml)
1497
def reconcile(self, other=None, thorough=False):
1499
return self._real_repository.reconcile(other=other, thorough=thorough)
1501
def all_revision_ids(self):
1503
return self._real_repository.all_revision_ids()
1506
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1508
return self._real_repository.get_deltas_for_revisions(revisions,
1509
specific_fileids=specific_fileids)
1512
def get_revision_delta(self, revision_id, specific_fileids=None):
1514
return self._real_repository.get_revision_delta(revision_id,
1515
specific_fileids=specific_fileids)
1518
def revision_trees(self, revision_ids):
1520
return self._real_repository.revision_trees(revision_ids)
1523
def get_revision_reconcile(self, revision_id):
1525
return self._real_repository.get_revision_reconcile(revision_id)
1528
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1530
return self._real_repository.check(revision_ids=revision_ids,
1531
callback_refs=callback_refs, check_repo=check_repo)
1533
def copy_content_into(self, destination, revision_id=None):
1535
return self._real_repository.copy_content_into(
1536
destination, revision_id=revision_id)
1538
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1539
# get a tarball of the remote repository, and copy from that into the
1541
from bzrlib import osutils
1543
# TODO: Maybe a progress bar while streaming the tarball?
1544
note("Copying repository content as tarball...")
1545
tar_file = self._get_tarball('bz2')
1546
if tar_file is None:
1548
destination = to_bzrdir.create_repository()
1550
tar = tarfile.open('repository', fileobj=tar_file,
1552
tmpdir = osutils.mkdtemp()
1554
_extract_tar(tar, tmpdir)
1555
tmp_bzrdir = BzrDir.open(tmpdir)
1556
tmp_repo = tmp_bzrdir.open_repository()
1557
tmp_repo.copy_content_into(destination, revision_id)
1559
osutils.rmtree(tmpdir)
1563
# TODO: Suggestion from john: using external tar is much faster than
1564
# python's tarfile library, but it may not work on windows.
1567
def inventories(self):
1568
"""Decorate the real repository for now.
1570
In the long term a full blown network facility is needed to
1571
avoid creating a real repository object locally.
1574
return self._real_repository.inventories
1577
def pack(self, hint=None):
1578
"""Compress the data within the repository.
1580
This is not currently implemented within the smart server.
1583
return self._real_repository.pack(hint=hint)
1586
def revisions(self):
1587
"""Decorate the real repository for now.
1589
In the short term this should become a real object to intercept graph
1592
In the long term a full blown network facility is needed.
1595
return self._real_repository.revisions
1597
def set_make_working_trees(self, new_value):
1599
new_value_str = "True"
1601
new_value_str = "False"
1602
path = self.bzrdir._path_for_remote_call(self._client)
1604
response = self._call(
1605
'Repository.set_make_working_trees', path, new_value_str)
1606
except errors.UnknownSmartMethod:
1608
self._real_repository.set_make_working_trees(new_value)
1610
if response[0] != 'ok':
1611
raise errors.UnexpectedSmartServerResponse(response)
1614
def signatures(self):
1615
"""Decorate the real repository for now.
1617
In the long term a full blown network facility is needed to avoid
1618
creating a real repository object locally.
1621
return self._real_repository.signatures
1624
def sign_revision(self, revision_id, gpg_strategy):
1626
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1630
"""Decorate the real repository for now.
1632
In the long term a full blown network facility is needed to avoid
1633
creating a real repository object locally.
1636
return self._real_repository.texts
1639
def get_revisions(self, revision_ids):
1641
return self._real_repository.get_revisions(revision_ids)
1643
def supports_rich_root(self):
1644
return self._format.rich_root_data
1646
def iter_reverse_revision_history(self, revision_id):
1648
return self._real_repository.iter_reverse_revision_history(revision_id)
1651
def _serializer(self):
1652
return self._format._serializer
1654
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1656
return self._real_repository.store_revision_signature(
1657
gpg_strategy, plaintext, revision_id)
1659
def add_signature_text(self, revision_id, signature):
1661
return self._real_repository.add_signature_text(revision_id, signature)
1663
def has_signature_for_revision_id(self, revision_id):
1665
return self._real_repository.has_signature_for_revision_id(revision_id)
1667
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1669
return self._real_repository.item_keys_introduced_by(revision_ids,
1670
_files_pb=_files_pb)
1672
def revision_graph_can_have_wrong_parents(self):
1673
# The answer depends on the remote repo format.
1675
return self._real_repository.revision_graph_can_have_wrong_parents()
1677
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1679
return self._real_repository._find_inconsistent_revision_parents(
1682
def _check_for_inconsistent_revision_parents(self):
1684
return self._real_repository._check_for_inconsistent_revision_parents()
1686
def _make_parents_provider(self, other=None):
1687
providers = [self._unstacked_provider]
1688
if other is not None:
1689
providers.insert(0, other)
1690
providers.extend(r._make_parents_provider() for r in
1691
self._fallback_repositories)
1692
return graph.StackedParentsProvider(providers)
1694
def _serialise_search_recipe(self, recipe):
1695
"""Serialise a graph search recipe.
1697
:param recipe: A search recipe (start, stop, count).
1698
:return: Serialised bytes.
1700
start_keys = ' '.join(recipe[1])
1701
stop_keys = ' '.join(recipe[2])
1702
count = str(recipe[3])
1703
return '\n'.join((start_keys, stop_keys, count))
1705
def _serialise_search_result(self, search_result):
1706
if isinstance(search_result, graph.PendingAncestryResult):
1707
parts = ['ancestry-of']
1708
parts.extend(search_result.heads)
1710
recipe = search_result.get_recipe()
1711
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1712
return '\n'.join(parts)
1715
path = self.bzrdir._path_for_remote_call(self._client)
1717
response = self._call('PackRepository.autopack', path)
1718
except errors.UnknownSmartMethod:
1720
self._real_repository._pack_collection.autopack()
1723
if response[0] != 'ok':
1724
raise errors.UnexpectedSmartServerResponse(response)
1727
class RemoteStreamSink(repository.StreamSink):
1729
def _insert_real(self, stream, src_format, resume_tokens):
1730
self.target_repo._ensure_real()
1731
sink = self.target_repo._real_repository._get_sink()
1732
result = sink.insert_stream(stream, src_format, resume_tokens)
1734
self.target_repo.autopack()
1737
def insert_stream(self, stream, src_format, resume_tokens):
1738
target = self.target_repo
1739
target._unstacked_provider.missing_keys.clear()
1740
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1741
if target._lock_token:
1742
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1743
lock_args = (target._lock_token or '',)
1745
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1747
client = target._client
1748
medium = client._medium
1749
path = target.bzrdir._path_for_remote_call(client)
1750
# Probe for the verb to use with an empty stream before sending the
1751
# real stream to it. We do this both to avoid the risk of sending a
1752
# large request that is then rejected, and because we don't want to
1753
# implement a way to buffer, rewind, or restart the stream.
1755
for verb, required_version in candidate_calls:
1756
if medium._is_remote_before(required_version):
1759
# We've already done the probing (and set _is_remote_before) on
1760
# a previous insert.
1763
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1765
response = client.call_with_body_stream(
1766
(verb, path, '') + lock_args, byte_stream)
1767
except errors.UnknownSmartMethod:
1768
medium._remember_remote_is_before(required_version)
1774
return self._insert_real(stream, src_format, resume_tokens)
1775
self._last_inv_record = None
1776
self._last_substream = None
1777
if required_version < (1, 19):
1778
# Remote side doesn't support inventory deltas. Wrap the stream to
1779
# make sure we don't send any. If the stream contains inventory
1780
# deltas we'll interrupt the smart insert_stream request and
1782
stream = self._stop_stream_if_inventory_delta(stream)
1783
byte_stream = smart_repo._stream_to_byte_stream(
1785
resume_tokens = ' '.join(resume_tokens)
1786
response = client.call_with_body_stream(
1787
(verb, path, resume_tokens) + lock_args, byte_stream)
1788
if response[0][0] not in ('ok', 'missing-basis'):
1789
raise errors.UnexpectedSmartServerResponse(response)
1790
if self._last_substream is not None:
1791
# The stream included an inventory-delta record, but the remote
1792
# side isn't new enough to support them. So we need to send the
1793
# rest of the stream via VFS.
1794
self.target_repo.refresh_data()
1795
return self._resume_stream_with_vfs(response, src_format)
1796
if response[0][0] == 'missing-basis':
1797
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1798
resume_tokens = tokens
1799
return resume_tokens, set(missing_keys)
1801
self.target_repo.refresh_data()
1804
def _resume_stream_with_vfs(self, response, src_format):
1805
"""Resume sending a stream via VFS, first resending the record and
1806
substream that couldn't be sent via an insert_stream verb.
1808
if response[0][0] == 'missing-basis':
1809
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1810
# Ignore missing_keys, we haven't finished inserting yet
1813
def resume_substream():
1814
# Yield the substream that was interrupted.
1815
for record in self._last_substream:
1817
self._last_substream = None
1818
def resume_stream():
1819
# Finish sending the interrupted substream
1820
yield ('inventory-deltas', resume_substream())
1821
# Then simply continue sending the rest of the stream.
1822
for substream_kind, substream in self._last_stream:
1823
yield substream_kind, substream
1824
return self._insert_real(resume_stream(), src_format, tokens)
1826
def _stop_stream_if_inventory_delta(self, stream):
1827
"""Normally this just lets the original stream pass-through unchanged.
1829
However if any 'inventory-deltas' substream occurs it will stop
1830
streaming, and store the interrupted substream and stream in
1831
self._last_substream and self._last_stream so that the stream can be
1832
resumed by _resume_stream_with_vfs.
1835
stream_iter = iter(stream)
1836
for substream_kind, substream in stream_iter:
1837
if substream_kind == 'inventory-deltas':
1838
self._last_substream = substream
1839
self._last_stream = stream_iter
1842
yield substream_kind, substream
1845
class RemoteStreamSource(repository.StreamSource):
1846
"""Stream data from a remote server."""
1848
def get_stream(self, search):
1849
if (self.from_repository._fallback_repositories and
1850
self.to_format._fetch_order == 'topological'):
1851
return self._real_stream(self.from_repository, search)
1854
repos = [self.from_repository]
1860
repos.extend(repo._fallback_repositories)
1861
sources.append(repo)
1862
return self.missing_parents_chain(search, sources)
1864
def get_stream_for_missing_keys(self, missing_keys):
1865
self.from_repository._ensure_real()
1866
real_repo = self.from_repository._real_repository
1867
real_source = real_repo._get_source(self.to_format)
1868
return real_source.get_stream_for_missing_keys(missing_keys)
1870
def _real_stream(self, repo, search):
1871
"""Get a stream for search from repo.
1873
This never called RemoteStreamSource.get_stream, and is a heler
1874
for RemoteStreamSource._get_stream to allow getting a stream
1875
reliably whether fallback back because of old servers or trying
1876
to stream from a non-RemoteRepository (which the stacked support
1879
source = repo._get_source(self.to_format)
1880
if isinstance(source, RemoteStreamSource):
1882
source = repo._real_repository._get_source(self.to_format)
1883
return source.get_stream(search)
1885
def _get_stream(self, repo, search):
1886
"""Core worker to get a stream from repo for search.
1888
This is used by both get_stream and the stacking support logic. It
1889
deliberately gets a stream for repo which does not need to be
1890
self.from_repository. In the event that repo is not Remote, or
1891
cannot do a smart stream, a fallback is made to the generic
1892
repository._get_stream() interface, via self._real_stream.
1894
In the event of stacking, streams from _get_stream will not
1895
contain all the data for search - this is normal (see get_stream).
1897
:param repo: A repository.
1898
:param search: A search.
1900
# Fallbacks may be non-smart
1901
if not isinstance(repo, RemoteRepository):
1902
return self._real_stream(repo, search)
1903
client = repo._client
1904
medium = client._medium
1905
path = repo.bzrdir._path_for_remote_call(client)
1906
search_bytes = repo._serialise_search_result(search)
1907
args = (path, self.to_format.network_name())
1909
('Repository.get_stream_1.19', (1, 19)),
1910
('Repository.get_stream', (1, 13))]
1912
for verb, version in candidate_verbs:
1913
if medium._is_remote_before(version):
1916
response = repo._call_with_body_bytes_expecting_body(
1917
verb, args, search_bytes)
1918
except errors.UnknownSmartMethod:
1919
medium._remember_remote_is_before(version)
1921
response_tuple, response_handler = response
1925
return self._real_stream(repo, search)
1926
if response_tuple[0] != 'ok':
1927
raise errors.UnexpectedSmartServerResponse(response_tuple)
1928
byte_stream = response_handler.read_streamed_body()
1929
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1930
if src_format.network_name() != repo._format.network_name():
1931
raise AssertionError(
1932
"Mismatched RemoteRepository and stream src %r, %r" % (
1933
src_format.network_name(), repo._format.network_name()))
1936
def missing_parents_chain(self, search, sources):
1937
"""Chain multiple streams together to handle stacking.
1939
:param search: The overall search to satisfy with streams.
1940
:param sources: A list of Repository objects to query.
1942
self.from_serialiser = self.from_repository._format._serializer
1943
self.seen_revs = set()
1944
self.referenced_revs = set()
1945
# If there are heads in the search, or the key count is > 0, we are not
1947
while not search.is_empty() and len(sources) > 1:
1948
source = sources.pop(0)
1949
stream = self._get_stream(source, search)
1950
for kind, substream in stream:
1951
if kind != 'revisions':
1952
yield kind, substream
1954
yield kind, self.missing_parents_rev_handler(substream)
1955
search = search.refine(self.seen_revs, self.referenced_revs)
1956
self.seen_revs = set()
1957
self.referenced_revs = set()
1958
if not search.is_empty():
1959
for kind, stream in self._get_stream(sources[0], search):
1962
def missing_parents_rev_handler(self, substream):
1963
for content in substream:
1964
revision_bytes = content.get_bytes_as('fulltext')
1965
revision = self.from_serialiser.read_revision_from_string(
1967
self.seen_revs.add(content.key[-1])
1968
self.referenced_revs.update(revision.parent_ids)
1972
class RemoteBranchLockableFiles(LockableFiles):
1973
"""A 'LockableFiles' implementation that talks to a smart server.
1975
This is not a public interface class.
1978
def __init__(self, bzrdir, _client):
1979
self.bzrdir = bzrdir
1980
self._client = _client
1981
self._need_find_modes = True
1982
LockableFiles.__init__(
1983
self, bzrdir.get_branch_transport(None),
1984
'lock', lockdir.LockDir)
1986
def _find_modes(self):
1987
# RemoteBranches don't let the client set the mode of control files.
1988
self._dir_mode = None
1989
self._file_mode = None
1992
class RemoteBranchFormat(branch.BranchFormat):
1994
def __init__(self, network_name=None):
1995
super(RemoteBranchFormat, self).__init__()
1996
self._matchingbzrdir = RemoteBzrDirFormat()
1997
self._matchingbzrdir.set_branch_format(self)
1998
self._custom_format = None
1999
self._network_name = network_name
2001
def __eq__(self, other):
2002
return (isinstance(other, RemoteBranchFormat) and
2003
self.__dict__ == other.__dict__)
2005
def _ensure_real(self):
2006
if self._custom_format is None:
2007
self._custom_format = branch.network_format_registry.get(
2010
def get_format_description(self):
2012
return 'Remote: ' + self._custom_format.get_format_description()
2014
def network_name(self):
2015
return self._network_name
2017
def open(self, a_bzrdir, ignore_fallbacks=False):
2018
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2020
def _vfs_initialize(self, a_bzrdir):
2021
# Initialisation when using a local bzrdir object, or a non-vfs init
2022
# method is not available on the server.
2023
# self._custom_format is always set - the start of initialize ensures
2025
if isinstance(a_bzrdir, RemoteBzrDir):
2026
a_bzrdir._ensure_real()
2027
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2029
# We assume the bzrdir is parameterised; it may not be.
2030
result = self._custom_format.initialize(a_bzrdir)
2031
if (isinstance(a_bzrdir, RemoteBzrDir) and
2032
not isinstance(result, RemoteBranch)):
2033
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
2036
def initialize(self, a_bzrdir):
2037
# 1) get the network name to use.
2038
if self._custom_format:
2039
network_name = self._custom_format.network_name()
2041
# Select the current bzrlib default and ask for that.
2042
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2043
reference_format = reference_bzrdir_format.get_branch_format()
2044
self._custom_format = reference_format
2045
network_name = reference_format.network_name()
2046
# Being asked to create on a non RemoteBzrDir:
2047
if not isinstance(a_bzrdir, RemoteBzrDir):
2048
return self._vfs_initialize(a_bzrdir)
2049
medium = a_bzrdir._client._medium
2050
if medium._is_remote_before((1, 13)):
2051
return self._vfs_initialize(a_bzrdir)
2052
# Creating on a remote bzr dir.
2053
# 2) try direct creation via RPC
2054
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2055
verb = 'BzrDir.create_branch'
2057
response = a_bzrdir._call(verb, path, network_name)
2058
except errors.UnknownSmartMethod:
2059
# Fallback - use vfs methods
2060
medium._remember_remote_is_before((1, 13))
2061
return self._vfs_initialize(a_bzrdir)
2062
if response[0] != 'ok':
2063
raise errors.UnexpectedSmartServerResponse(response)
2064
# Turn the response into a RemoteRepository object.
2065
format = RemoteBranchFormat(network_name=response[1])
2066
repo_format = response_tuple_to_repo_format(response[3:])
2067
if response[2] == '':
2068
repo_bzrdir = a_bzrdir
2070
repo_bzrdir = RemoteBzrDir(
2071
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2073
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2074
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2075
format=format, setup_stacking=False)
2076
# XXX: We know this is a new branch, so it must have revno 0, revid
2077
# NULL_REVISION. Creating the branch locked would make this be unable
2078
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2079
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2080
return remote_branch
2082
def make_tags(self, branch):
2084
return self._custom_format.make_tags(branch)
2086
def supports_tags(self):
2087
# Remote branches might support tags, but we won't know until we
2088
# access the real remote branch.
2090
return self._custom_format.supports_tags()
2092
def supports_stacking(self):
2094
return self._custom_format.supports_stacking()
2096
def supports_set_append_revisions_only(self):
2098
return self._custom_format.supports_set_append_revisions_only()
2101
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2102
"""Branch stored on a server accessed by HPSS RPC.
2104
At the moment most operations are mapped down to simple file operations.
2107
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2108
_client=None, format=None, setup_stacking=True):
2109
"""Create a RemoteBranch instance.
2111
:param real_branch: An optional local implementation of the branch
2112
format, usually accessing the data via the VFS.
2113
:param _client: Private parameter for testing.
2114
:param format: A RemoteBranchFormat object, None to create one
2115
automatically. If supplied it should have a network_name already
2117
:param setup_stacking: If True make an RPC call to determine the
2118
stacked (or not) status of the branch. If False assume the branch
2121
# We intentionally don't call the parent class's __init__, because it
2122
# will try to assign to self.tags, which is a property in this subclass.
2123
# And the parent's __init__ doesn't do much anyway.
2124
self.bzrdir = remote_bzrdir
2125
if _client is not None:
2126
self._client = _client
2128
self._client = remote_bzrdir._client
2129
self.repository = remote_repository
2130
if real_branch is not None:
2131
self._real_branch = real_branch
2132
# Give the remote repository the matching real repo.
2133
real_repo = self._real_branch.repository
2134
if isinstance(real_repo, RemoteRepository):
2135
real_repo._ensure_real()
2136
real_repo = real_repo._real_repository
2137
self.repository._set_real_repository(real_repo)
2138
# Give the branch the remote repository to let fast-pathing happen.
2139
self._real_branch.repository = self.repository
2141
self._real_branch = None
2142
# Fill out expected attributes of branch for bzrlib API users.
2143
self._clear_cached_state()
2144
self.base = self.bzrdir.root_transport.base
2145
self._control_files = None
2146
self._lock_mode = None
2147
self._lock_token = None
2148
self._repo_lock_token = None
2149
self._lock_count = 0
2150
self._leave_lock = False
2151
# Setup a format: note that we cannot call _ensure_real until all the
2152
# attributes above are set: This code cannot be moved higher up in this
2155
self._format = RemoteBranchFormat()
2156
if real_branch is not None:
2157
self._format._network_name = \
2158
self._real_branch._format.network_name()
2160
self._format = format
2161
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2162
# branch.open_branch method.
2163
self._real_ignore_fallbacks = not setup_stacking
2164
if not self._format._network_name:
2165
# Did not get from open_branchV2 - old server.
2167
self._format._network_name = \
2168
self._real_branch._format.network_name()
2169
self.tags = self._format.make_tags(self)
2170
# The base class init is not called, so we duplicate this:
2171
hooks = branch.Branch.hooks['open']
2174
self._is_stacked = False
2176
self._setup_stacking()
2178
def _setup_stacking(self):
2179
# configure stacking into the remote repository, by reading it from
2182
fallback_url = self.get_stacked_on_url()
2183
except (errors.NotStacked, errors.UnstackableBranchFormat,
2184
errors.UnstackableRepositoryFormat), e:
2186
self._is_stacked = True
2187
self._activate_fallback_location(fallback_url)
2189
def _get_config(self):
2190
return RemoteBranchConfig(self)
2192
def _get_real_transport(self):
2193
# if we try vfs access, return the real branch's vfs transport
2195
return self._real_branch._transport
2197
_transport = property(_get_real_transport)
2200
return "%s(%s)" % (self.__class__.__name__, self.base)
2204
def _ensure_real(self):
2205
"""Ensure that there is a _real_branch set.
2207
Used before calls to self._real_branch.
2209
if self._real_branch is None:
2210
if not vfs.vfs_enabled():
2211
raise AssertionError('smart server vfs must be enabled '
2212
'to use vfs implementation')
2213
self.bzrdir._ensure_real()
2214
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2215
ignore_fallbacks=self._real_ignore_fallbacks)
2216
if self.repository._real_repository is None:
2217
# Give the remote repository the matching real repo.
2218
real_repo = self._real_branch.repository
2219
if isinstance(real_repo, RemoteRepository):
2220
real_repo._ensure_real()
2221
real_repo = real_repo._real_repository
2222
self.repository._set_real_repository(real_repo)
2223
# Give the real branch the remote repository to let fast-pathing
2225
self._real_branch.repository = self.repository
2226
if self._lock_mode == 'r':
2227
self._real_branch.lock_read()
2228
elif self._lock_mode == 'w':
2229
self._real_branch.lock_write(token=self._lock_token)
2231
def _translate_error(self, err, **context):
2232
self.repository._translate_error(err, branch=self, **context)
2234
def _clear_cached_state(self):
2235
super(RemoteBranch, self)._clear_cached_state()
2236
if self._real_branch is not None:
2237
self._real_branch._clear_cached_state()
2239
def _clear_cached_state_of_remote_branch_only(self):
2240
"""Like _clear_cached_state, but doesn't clear the cache of
2243
This is useful when falling back to calling a method of
2244
self._real_branch that changes state. In that case the underlying
2245
branch changes, so we need to invalidate this RemoteBranch's cache of
2246
it. However, there's no need to invalidate the _real_branch's cache
2247
too, in fact doing so might harm performance.
2249
super(RemoteBranch, self)._clear_cached_state()
2252
def control_files(self):
2253
# Defer actually creating RemoteBranchLockableFiles until its needed,
2254
# because it triggers an _ensure_real that we otherwise might not need.
2255
if self._control_files is None:
2256
self._control_files = RemoteBranchLockableFiles(
2257
self.bzrdir, self._client)
2258
return self._control_files
2260
def _get_checkout_format(self):
2262
return self._real_branch._get_checkout_format()
2264
def get_physical_lock_status(self):
2265
"""See Branch.get_physical_lock_status()."""
2266
# should be an API call to the server, as branches must be lockable.
2268
return self._real_branch.get_physical_lock_status()
2270
def get_stacked_on_url(self):
2271
"""Get the URL this branch is stacked against.
2273
:raises NotStacked: If the branch is not stacked.
2274
:raises UnstackableBranchFormat: If the branch does not support
2276
:raises UnstackableRepositoryFormat: If the repository does not support
2280
# there may not be a repository yet, so we can't use
2281
# self._translate_error, so we can't use self._call either.
2282
response = self._client.call('Branch.get_stacked_on_url',
2283
self._remote_path())
2284
except errors.ErrorFromSmartServer, err:
2285
# there may not be a repository yet, so we can't call through
2286
# its _translate_error
2287
_translate_error(err, branch=self)
2288
except errors.UnknownSmartMethod, err:
2290
return self._real_branch.get_stacked_on_url()
2291
if response[0] != 'ok':
2292
raise errors.UnexpectedSmartServerResponse(response)
2295
def set_stacked_on_url(self, url):
2296
branch.Branch.set_stacked_on_url(self, url)
2298
self._is_stacked = False
2300
self._is_stacked = True
2302
def _vfs_get_tags_bytes(self):
2304
return self._real_branch._get_tags_bytes()
2306
def _get_tags_bytes(self):
2307
medium = self._client._medium
2308
if medium._is_remote_before((1, 13)):
2309
return self._vfs_get_tags_bytes()
2311
response = self._call('Branch.get_tags_bytes', self._remote_path())
2312
except errors.UnknownSmartMethod:
2313
medium._remember_remote_is_before((1, 13))
2314
return self._vfs_get_tags_bytes()
2317
def _vfs_set_tags_bytes(self, bytes):
2319
return self._real_branch._set_tags_bytes(bytes)
2321
def _set_tags_bytes(self, bytes):
2322
medium = self._client._medium
2323
if medium._is_remote_before((1, 18)):
2324
self._vfs_set_tags_bytes(bytes)
2328
self._remote_path(), self._lock_token, self._repo_lock_token)
2329
response = self._call_with_body_bytes(
2330
'Branch.set_tags_bytes', args, bytes)
2331
except errors.UnknownSmartMethod:
2332
medium._remember_remote_is_before((1, 18))
2333
self._vfs_set_tags_bytes(bytes)
2335
def lock_read(self):
2336
self.repository.lock_read()
2337
if not self._lock_mode:
2338
self._note_lock('r')
2339
self._lock_mode = 'r'
2340
self._lock_count = 1
2341
if self._real_branch is not None:
2342
self._real_branch.lock_read()
2344
self._lock_count += 1
2346
def _remote_lock_write(self, token):
2348
branch_token = repo_token = ''
2350
branch_token = token
2351
repo_token = self.repository.lock_write()
2352
self.repository.unlock()
2353
err_context = {'token': token}
2354
response = self._call(
2355
'Branch.lock_write', self._remote_path(), branch_token,
2356
repo_token or '', **err_context)
2357
if response[0] != 'ok':
2358
raise errors.UnexpectedSmartServerResponse(response)
2359
ok, branch_token, repo_token = response
2360
return branch_token, repo_token
2362
def lock_write(self, token=None):
2363
if not self._lock_mode:
2364
self._note_lock('w')
2365
# Lock the branch and repo in one remote call.
2366
remote_tokens = self._remote_lock_write(token)
2367
self._lock_token, self._repo_lock_token = remote_tokens
2368
if not self._lock_token:
2369
raise SmartProtocolError('Remote server did not return a token!')
2370
# Tell the self.repository object that it is locked.
2371
self.repository.lock_write(
2372
self._repo_lock_token, _skip_rpc=True)
2374
if self._real_branch is not None:
2375
self._real_branch.lock_write(token=self._lock_token)
2376
if token is not None:
2377
self._leave_lock = True
2379
self._leave_lock = False
2380
self._lock_mode = 'w'
2381
self._lock_count = 1
2382
elif self._lock_mode == 'r':
2383
raise errors.ReadOnlyTransaction
2385
if token is not None:
2386
# A token was given to lock_write, and we're relocking, so
2387
# check that the given token actually matches the one we
2389
if token != self._lock_token:
2390
raise errors.TokenMismatch(token, self._lock_token)
2391
self._lock_count += 1
2392
# Re-lock the repository too.
2393
self.repository.lock_write(self._repo_lock_token)
2394
return self._lock_token or None
2396
def _unlock(self, branch_token, repo_token):
2397
err_context = {'token': str((branch_token, repo_token))}
2398
response = self._call(
2399
'Branch.unlock', self._remote_path(), branch_token,
2400
repo_token or '', **err_context)
2401
if response == ('ok',):
2403
raise errors.UnexpectedSmartServerResponse(response)
2405
@only_raises(errors.LockNotHeld, errors.LockBroken)
2408
self._lock_count -= 1
2409
if not self._lock_count:
2410
self._clear_cached_state()
2411
mode = self._lock_mode
2412
self._lock_mode = None
2413
if self._real_branch is not None:
2414
if (not self._leave_lock and mode == 'w' and
2415
self._repo_lock_token):
2416
# If this RemoteBranch will remove the physical lock
2417
# for the repository, make sure the _real_branch
2418
# doesn't do it first. (Because the _real_branch's
2419
# repository is set to be the RemoteRepository.)
2420
self._real_branch.repository.leave_lock_in_place()
2421
self._real_branch.unlock()
2423
# Only write-locked branched need to make a remote method
2424
# call to perform the unlock.
2426
if not self._lock_token:
2427
raise AssertionError('Locked, but no token!')
2428
branch_token = self._lock_token
2429
repo_token = self._repo_lock_token
2430
self._lock_token = None
2431
self._repo_lock_token = None
2432
if not self._leave_lock:
2433
self._unlock(branch_token, repo_token)
2435
self.repository.unlock()
2437
def break_lock(self):
2439
return self._real_branch.break_lock()
2441
def leave_lock_in_place(self):
2442
if not self._lock_token:
2443
raise NotImplementedError(self.leave_lock_in_place)
2444
self._leave_lock = True
2446
def dont_leave_lock_in_place(self):
2447
if not self._lock_token:
2448
raise NotImplementedError(self.dont_leave_lock_in_place)
2449
self._leave_lock = False
2452
def get_rev_id(self, revno, history=None):
2454
return _mod_revision.NULL_REVISION
2455
last_revision_info = self.last_revision_info()
2456
ok, result = self.repository.get_rev_id_for_revno(
2457
revno, last_revision_info)
2460
missing_parent = result[1]
2461
# Either the revision named by the server is missing, or its parent
2462
# is. Call get_parent_map to determine which, so that we report a
2464
parent_map = self.repository.get_parent_map([missing_parent])
2465
if missing_parent in parent_map:
2466
missing_parent = parent_map[missing_parent]
2467
raise errors.RevisionNotPresent(missing_parent, self.repository)
2469
def _last_revision_info(self):
2470
response = self._call('Branch.last_revision_info', self._remote_path())
2471
if response[0] != 'ok':
2472
raise SmartProtocolError('unexpected response code %s' % (response,))
2473
revno = int(response[1])
2474
last_revision = response[2]
2475
return (revno, last_revision)
2477
def _gen_revision_history(self):
2478
"""See Branch._gen_revision_history()."""
2479
if self._is_stacked:
2481
return self._real_branch._gen_revision_history()
2482
response_tuple, response_handler = self._call_expecting_body(
2483
'Branch.revision_history', self._remote_path())
2484
if response_tuple[0] != 'ok':
2485
raise errors.UnexpectedSmartServerResponse(response_tuple)
2486
result = response_handler.read_body_bytes().split('\x00')
2491
def _remote_path(self):
2492
return self.bzrdir._path_for_remote_call(self._client)
2494
def _set_last_revision_descendant(self, revision_id, other_branch,
2495
allow_diverged=False, allow_overwrite_descendant=False):
2496
# This performs additional work to meet the hook contract; while its
2497
# undesirable, we have to synthesise the revno to call the hook, and
2498
# not calling the hook is worse as it means changes can't be prevented.
2499
# Having calculated this though, we can't just call into
2500
# set_last_revision_info as a simple call, because there is a set_rh
2501
# hook that some folk may still be using.
2502
old_revno, old_revid = self.last_revision_info()
2503
history = self._lefthand_history(revision_id)
2504
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2505
err_context = {'other_branch': other_branch}
2506
response = self._call('Branch.set_last_revision_ex',
2507
self._remote_path(), self._lock_token, self._repo_lock_token,
2508
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2510
self._clear_cached_state()
2511
if len(response) != 3 and response[0] != 'ok':
2512
raise errors.UnexpectedSmartServerResponse(response)
2513
new_revno, new_revision_id = response[1:]
2514
self._last_revision_info_cache = new_revno, new_revision_id
2515
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2516
if self._real_branch is not None:
2517
cache = new_revno, new_revision_id
2518
self._real_branch._last_revision_info_cache = cache
2520
def _set_last_revision(self, revision_id):
2521
old_revno, old_revid = self.last_revision_info()
2522
# This performs additional work to meet the hook contract; while its
2523
# undesirable, we have to synthesise the revno to call the hook, and
2524
# not calling the hook is worse as it means changes can't be prevented.
2525
# Having calculated this though, we can't just call into
2526
# set_last_revision_info as a simple call, because there is a set_rh
2527
# hook that some folk may still be using.
2528
history = self._lefthand_history(revision_id)
2529
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2530
self._clear_cached_state()
2531
response = self._call('Branch.set_last_revision',
2532
self._remote_path(), self._lock_token, self._repo_lock_token,
2534
if response != ('ok',):
2535
raise errors.UnexpectedSmartServerResponse(response)
2536
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2539
def set_revision_history(self, rev_history):
2540
# Send just the tip revision of the history; the server will generate
2541
# the full history from that. If the revision doesn't exist in this
2542
# branch, NoSuchRevision will be raised.
2543
if rev_history == []:
2546
rev_id = rev_history[-1]
2547
self._set_last_revision(rev_id)
2548
for hook in branch.Branch.hooks['set_rh']:
2549
hook(self, rev_history)
2550
self._cache_revision_history(rev_history)
2552
def _get_parent_location(self):
2553
medium = self._client._medium
2554
if medium._is_remote_before((1, 13)):
2555
return self._vfs_get_parent_location()
2557
response = self._call('Branch.get_parent', self._remote_path())
2558
except errors.UnknownSmartMethod:
2559
medium._remember_remote_is_before((1, 13))
2560
return self._vfs_get_parent_location()
2561
if len(response) != 1:
2562
raise errors.UnexpectedSmartServerResponse(response)
2563
parent_location = response[0]
2564
if parent_location == '':
2566
return parent_location
2568
def _vfs_get_parent_location(self):
2570
return self._real_branch._get_parent_location()
2572
def _set_parent_location(self, url):
2573
medium = self._client._medium
2574
if medium._is_remote_before((1, 15)):
2575
return self._vfs_set_parent_location(url)
2577
call_url = url or ''
2578
if type(call_url) is not str:
2579
raise AssertionError('url must be a str or None (%s)' % url)
2580
response = self._call('Branch.set_parent_location',
2581
self._remote_path(), self._lock_token, self._repo_lock_token,
2583
except errors.UnknownSmartMethod:
2584
medium._remember_remote_is_before((1, 15))
2585
return self._vfs_set_parent_location(url)
2587
raise errors.UnexpectedSmartServerResponse(response)
2589
def _vfs_set_parent_location(self, url):
2591
return self._real_branch._set_parent_location(url)
2594
def pull(self, source, overwrite=False, stop_revision=None,
2596
self._clear_cached_state_of_remote_branch_only()
2598
return self._real_branch.pull(
2599
source, overwrite=overwrite, stop_revision=stop_revision,
2600
_override_hook_target=self, **kwargs)
2603
def push(self, target, overwrite=False, stop_revision=None):
2605
return self._real_branch.push(
2606
target, overwrite=overwrite, stop_revision=stop_revision,
2607
_override_hook_source_branch=self)
2609
def is_locked(self):
2610
return self._lock_count >= 1
2613
def revision_id_to_revno(self, revision_id):
2615
return self._real_branch.revision_id_to_revno(revision_id)
2618
def set_last_revision_info(self, revno, revision_id):
2619
# XXX: These should be returned by the set_last_revision_info verb
2620
old_revno, old_revid = self.last_revision_info()
2621
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2622
revision_id = ensure_null(revision_id)
2624
response = self._call('Branch.set_last_revision_info',
2625
self._remote_path(), self._lock_token, self._repo_lock_token,
2626
str(revno), revision_id)
2627
except errors.UnknownSmartMethod:
2629
self._clear_cached_state_of_remote_branch_only()
2630
self._real_branch.set_last_revision_info(revno, revision_id)
2631
self._last_revision_info_cache = revno, revision_id
2633
if response == ('ok',):
2634
self._clear_cached_state()
2635
self._last_revision_info_cache = revno, revision_id
2636
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2637
# Update the _real_branch's cache too.
2638
if self._real_branch is not None:
2639
cache = self._last_revision_info_cache
2640
self._real_branch._last_revision_info_cache = cache
2642
raise errors.UnexpectedSmartServerResponse(response)
2645
def generate_revision_history(self, revision_id, last_rev=None,
2647
medium = self._client._medium
2648
if not medium._is_remote_before((1, 6)):
2649
# Use a smart method for 1.6 and above servers
2651
self._set_last_revision_descendant(revision_id, other_branch,
2652
allow_diverged=True, allow_overwrite_descendant=True)
2654
except errors.UnknownSmartMethod:
2655
medium._remember_remote_is_before((1, 6))
2656
self._clear_cached_state_of_remote_branch_only()
2657
self.set_revision_history(self._lefthand_history(revision_id,
2658
last_rev=last_rev,other_branch=other_branch))
2660
def set_push_location(self, location):
2662
return self._real_branch.set_push_location(location)
2665
class RemoteConfig(object):
2666
"""A Config that reads and writes from smart verbs.
2668
It is a low-level object that considers config data to be name/value pairs
2669
that may be associated with a section. Assigning meaning to the these
2670
values is done at higher levels like bzrlib.config.TreeConfig.
2673
def get_option(self, name, section=None, default=None):
2674
"""Return the value associated with a named option.
2676
:param name: The name of the value
2677
:param section: The section the option is in (if any)
2678
:param default: The value to return if the value is not set
2679
:return: The value or default value
2682
configobj = self._get_configobj()
2684
section_obj = configobj
2687
section_obj = configobj[section]
2690
return section_obj.get(name, default)
2691
except errors.UnknownSmartMethod:
2692
return self._vfs_get_option(name, section, default)
2694
def _response_to_configobj(self, response):
2695
if len(response[0]) and response[0][0] != 'ok':
2696
raise errors.UnexpectedSmartServerResponse(response)
2697
lines = response[1].read_body_bytes().splitlines()
2698
return config.ConfigObj(lines, encoding='utf-8')
2701
class RemoteBranchConfig(RemoteConfig):
2702
"""A RemoteConfig for Branches."""
2704
def __init__(self, branch):
2705
self._branch = branch
2707
def _get_configobj(self):
2708
path = self._branch._remote_path()
2709
response = self._branch._client.call_expecting_body(
2710
'Branch.get_config_file', path)
2711
return self._response_to_configobj(response)
2713
def set_option(self, value, name, section=None):
2714
"""Set the value associated with a named option.
2716
:param value: The value to set
2717
:param name: The name of the value to set
2718
:param section: The section the option is in (if any)
2720
medium = self._branch._client._medium
2721
if medium._is_remote_before((1, 14)):
2722
return self._vfs_set_option(value, name, section)
2724
path = self._branch._remote_path()
2725
response = self._branch._client.call('Branch.set_config_option',
2726
path, self._branch._lock_token, self._branch._repo_lock_token,
2727
value.encode('utf8'), name, section or '')
2728
except errors.UnknownSmartMethod:
2729
medium._remember_remote_is_before((1, 14))
2730
return self._vfs_set_option(value, name, section)
2732
raise errors.UnexpectedSmartServerResponse(response)
2734
def _real_object(self):
2735
self._branch._ensure_real()
2736
return self._branch._real_branch
2738
def _vfs_set_option(self, value, name, section=None):
2739
return self._real_object()._get_config().set_option(
2740
value, name, section)
2743
class RemoteBzrDirConfig(RemoteConfig):
2744
"""A RemoteConfig for BzrDirs."""
2746
def __init__(self, bzrdir):
2747
self._bzrdir = bzrdir
2749
def _get_configobj(self):
2750
medium = self._bzrdir._client._medium
2751
verb = 'BzrDir.get_config_file'
2752
if medium._is_remote_before((1, 15)):
2753
raise errors.UnknownSmartMethod(verb)
2754
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2755
response = self._bzrdir._call_expecting_body(
2757
return self._response_to_configobj(response)
2759
def _vfs_get_option(self, name, section, default):
2760
return self._real_object()._get_config().get_option(
2761
name, section, default)
2763
def set_option(self, value, name, section=None):
2764
"""Set the value associated with a named option.
2766
:param value: The value to set
2767
:param name: The name of the value to set
2768
:param section: The section the option is in (if any)
2770
return self._real_object()._get_config().set_option(
2771
value, name, section)
2773
def _real_object(self):
2774
self._bzrdir._ensure_real()
2775
return self._bzrdir._real_bzrdir
2779
def _extract_tar(tar, to_dir):
2780
"""Extract all the contents of a tarfile object.
2782
A replacement for extractall, which is not present in python2.4
2785
tar.extract(tarinfo, to_dir)
2788
def _translate_error(err, **context):
2789
"""Translate an ErrorFromSmartServer into a more useful error.
2791
Possible context keys:
2799
If the error from the server doesn't match a known pattern, then
2800
UnknownErrorFromSmartServer is raised.
2804
return context[name]
2805
except KeyError, key_err:
2806
mutter('Missing key %r in context %r', key_err.args[0], context)
2809
"""Get the path from the context if present, otherwise use first error
2813
return context['path']
2814
except KeyError, key_err:
2816
return err.error_args[0]
2817
except IndexError, idx_err:
2819
'Missing key %r in context %r', key_err.args[0], context)
2822
if err.error_verb == 'IncompatibleRepositories':
2823
raise errors.IncompatibleRepositories(err.error_args[0],
2824
err.error_args[1], err.error_args[2])
2825
elif err.error_verb == 'NoSuchRevision':
2826
raise NoSuchRevision(find('branch'), err.error_args[0])
2827
elif err.error_verb == 'nosuchrevision':
2828
raise NoSuchRevision(find('repository'), err.error_args[0])
2829
elif err.error_tuple == ('nobranch',):
2830
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2831
elif err.error_verb == 'norepository':
2832
raise errors.NoRepositoryPresent(find('bzrdir'))
2833
elif err.error_verb == 'LockContention':
2834
raise errors.LockContention('(remote lock)')
2835
elif err.error_verb == 'UnlockableTransport':
2836
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2837
elif err.error_verb == 'LockFailed':
2838
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2839
elif err.error_verb == 'TokenMismatch':
2840
raise errors.TokenMismatch(find('token'), '(remote token)')
2841
elif err.error_verb == 'Diverged':
2842
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2843
elif err.error_verb == 'TipChangeRejected':
2844
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2845
elif err.error_verb == 'UnstackableBranchFormat':
2846
raise errors.UnstackableBranchFormat(*err.error_args)
2847
elif err.error_verb == 'UnstackableRepositoryFormat':
2848
raise errors.UnstackableRepositoryFormat(*err.error_args)
2849
elif err.error_verb == 'NotStacked':
2850
raise errors.NotStacked(branch=find('branch'))
2851
elif err.error_verb == 'PermissionDenied':
2853
if len(err.error_args) >= 2:
2854
extra = err.error_args[1]
2857
raise errors.PermissionDenied(path, extra=extra)
2858
elif err.error_verb == 'ReadError':
2860
raise errors.ReadError(path)
2861
elif err.error_verb == 'NoSuchFile':
2863
raise errors.NoSuchFile(path)
2864
elif err.error_verb == 'FileExists':
2865
raise errors.FileExists(err.error_args[0])
2866
elif err.error_verb == 'DirectoryNotEmpty':
2867
raise errors.DirectoryNotEmpty(err.error_args[0])
2868
elif err.error_verb == 'ShortReadvError':
2869
args = err.error_args
2870
raise errors.ShortReadvError(
2871
args[0], int(args[1]), int(args[2]), int(args[3]))
2872
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2873
encoding = str(err.error_args[0]) # encoding must always be a string
2874
val = err.error_args[1]
2875
start = int(err.error_args[2])
2876
end = int(err.error_args[3])
2877
reason = str(err.error_args[4]) # reason must always be a string
2878
if val.startswith('u:'):
2879
val = val[2:].decode('utf-8')
2880
elif val.startswith('s:'):
2881
val = val[2:].decode('base64')
2882
if err.error_verb == 'UnicodeDecodeError':
2883
raise UnicodeDecodeError(encoding, val, start, end, reason)
2884
elif err.error_verb == 'UnicodeEncodeError':
2885
raise UnicodeEncodeError(encoding, val, start, end, reason)
2886
elif err.error_verb == 'ReadOnlyError':
2887
raise errors.TransportNotPossible('readonly transport')
2888
raise errors.UnknownErrorFromSmartServer(err)