1
# Copyright (C) 2006-2010 Canonical Ltd
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
# GNU General Public License for more details.
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
30
repository as _mod_repository,
32
revision as _mod_revision,
36
from bzrlib.branch import BranchReferenceFormat
37
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
39
from bzrlib.errors import (
43
from bzrlib.lockable_files import LockableFiles
44
from bzrlib.smart import client, vfs, repository as smart_repo
45
from bzrlib.revision import ensure_null, NULL_REVISION
46
from bzrlib.trace import mutter, note, warning
49
class _RpcHelper(object):
50
"""Mixin class that helps with issuing RPCs."""
52
def _call(self, method, *args, **err_context):
54
return self._client.call(method, *args)
55
except errors.ErrorFromSmartServer, err:
56
self._translate_error(err, **err_context)
58
def _call_expecting_body(self, method, *args, **err_context):
60
return self._client.call_expecting_body(method, *args)
61
except errors.ErrorFromSmartServer, err:
62
self._translate_error(err, **err_context)
64
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
66
return self._client.call_with_body_bytes(method, args, body_bytes)
67
except errors.ErrorFromSmartServer, err:
68
self._translate_error(err, **err_context)
70
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
73
return self._client.call_with_body_bytes_expecting_body(
74
method, args, body_bytes)
75
except errors.ErrorFromSmartServer, err:
76
self._translate_error(err, **err_context)
79
def response_tuple_to_repo_format(response):
80
"""Convert a response tuple describing a repository format to a format."""
81
format = RemoteRepositoryFormat()
82
format._rich_root_data = (response[0] == 'yes')
83
format._supports_tree_reference = (response[1] == 'yes')
84
format._supports_external_lookups = (response[2] == 'yes')
85
format._network_name = response[3]
89
# Note: RemoteBzrDirFormat is in bzrdir.py
91
class RemoteBzrDir(BzrDir, _RpcHelper):
92
"""Control directory on a remote server, accessed via bzr:// or similar."""
94
def __init__(self, transport, format, _client=None, _force_probe=False):
95
"""Construct a RemoteBzrDir.
97
:param _client: Private parameter for testing. Disables probing and the
100
BzrDir.__init__(self, transport, format)
101
# this object holds a delegated bzrdir that uses file-level operations
102
# to talk to the other side
103
self._real_bzrdir = None
104
self._has_working_tree = None
105
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
106
# create_branch for details.
107
self._next_open_branch_result = None
110
medium = transport.get_smart_medium()
111
self._client = client._SmartClient(medium)
113
self._client = _client
120
return '%s(%r)' % (self.__class__.__name__, self._client)
122
def _probe_bzrdir(self):
123
medium = self._client._medium
124
path = self._path_for_remote_call(self._client)
125
if medium._is_remote_before((2, 1)):
129
self._rpc_open_2_1(path)
131
except errors.UnknownSmartMethod:
132
medium._remember_remote_is_before((2, 1))
135
def _rpc_open_2_1(self, path):
136
response = self._call('BzrDir.open_2.1', path)
137
if response == ('no',):
138
raise errors.NotBranchError(path=self.root_transport.base)
139
elif response[0] == 'yes':
140
if response[1] == 'yes':
141
self._has_working_tree = True
142
elif response[1] == 'no':
143
self._has_working_tree = False
145
raise errors.UnexpectedSmartServerResponse(response)
147
raise errors.UnexpectedSmartServerResponse(response)
149
def _rpc_open(self, path):
150
response = self._call('BzrDir.open', path)
151
if response not in [('yes',), ('no',)]:
152
raise errors.UnexpectedSmartServerResponse(response)
153
if response == ('no',):
154
raise errors.NotBranchError(path=self.root_transport.base)
156
def _ensure_real(self):
157
"""Ensure that there is a _real_bzrdir set.
159
Used before calls to self._real_bzrdir.
161
if not self._real_bzrdir:
162
if 'hpssvfs' in debug.debug_flags:
164
warning('VFS BzrDir access triggered\n%s',
165
''.join(traceback.format_stack()))
166
self._real_bzrdir = BzrDir.open_from_transport(
167
self.root_transport, _server_formats=False)
168
self._format._network_name = \
169
self._real_bzrdir._format.network_name()
171
def _translate_error(self, err, **context):
172
_translate_error(err, bzrdir=self, **context)
174
def break_lock(self):
175
# Prevent aliasing problems in the next_open_branch_result cache.
176
# See create_branch for rationale.
177
self._next_open_branch_result = None
178
return BzrDir.break_lock(self)
180
def _vfs_cloning_metadir(self, require_stacking=False):
182
return self._real_bzrdir.cloning_metadir(
183
require_stacking=require_stacking)
185
def cloning_metadir(self, require_stacking=False):
186
medium = self._client._medium
187
if medium._is_remote_before((1, 13)):
188
return self._vfs_cloning_metadir(require_stacking=require_stacking)
189
verb = 'BzrDir.cloning_metadir'
194
path = self._path_for_remote_call(self._client)
196
response = self._call(verb, path, stacking)
197
except errors.UnknownSmartMethod:
198
medium._remember_remote_is_before((1, 13))
199
return self._vfs_cloning_metadir(require_stacking=require_stacking)
200
except errors.UnknownErrorFromSmartServer, err:
201
if err.error_tuple != ('BranchReference',):
203
# We need to resolve the branch reference to determine the
204
# cloning_metadir. This causes unnecessary RPCs to open the
205
# referenced branch (and bzrdir, etc) but only when the caller
206
# didn't already resolve the branch reference.
207
referenced_branch = self.open_branch()
208
return referenced_branch.bzrdir.cloning_metadir()
209
if len(response) != 3:
210
raise errors.UnexpectedSmartServerResponse(response)
211
control_name, repo_name, branch_info = response
212
if len(branch_info) != 2:
213
raise errors.UnexpectedSmartServerResponse(response)
214
branch_ref, branch_name = branch_info
215
format = bzrdir.network_format_registry.get(control_name)
217
format.repository_format = repository.network_format_registry.get(
219
if branch_ref == 'ref':
220
# XXX: we need possible_transports here to avoid reopening the
221
# connection to the referenced location
222
ref_bzrdir = BzrDir.open(branch_name)
223
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
224
format.set_branch_format(branch_format)
225
elif branch_ref == 'branch':
227
format.set_branch_format(
228
branch.network_format_registry.get(branch_name))
230
raise errors.UnexpectedSmartServerResponse(response)
233
def create_repository(self, shared=False):
234
# as per meta1 formats - just delegate to the format object which may
236
result = self._format.repository_format.initialize(self, shared)
237
if not isinstance(result, RemoteRepository):
238
return self.open_repository()
242
def destroy_repository(self):
243
"""See BzrDir.destroy_repository"""
245
self._real_bzrdir.destroy_repository()
247
def create_branch(self, name=None):
248
# as per meta1 formats - just delegate to the format object which may
250
real_branch = self._format.get_branch_format().initialize(self,
252
if not isinstance(real_branch, RemoteBranch):
253
result = RemoteBranch(self, self.find_repository(), real_branch,
257
# BzrDir.clone_on_transport() uses the result of create_branch but does
258
# not return it to its callers; we save approximately 8% of our round
259
# trips by handing the branch we created back to the first caller to
260
# open_branch rather than probing anew. Long term we need a API in
261
# bzrdir that doesn't discard result objects (like result_branch).
263
self._next_open_branch_result = result
266
def destroy_branch(self, name=None):
267
"""See BzrDir.destroy_branch"""
269
self._real_bzrdir.destroy_branch(name=name)
270
self._next_open_branch_result = None
272
def create_workingtree(self, revision_id=None, from_branch=None):
273
raise errors.NotLocalUrl(self.transport.base)
275
def find_branch_format(self):
276
"""Find the branch 'format' for this bzrdir.
278
This might be a synthetic object for e.g. RemoteBranch and SVN.
280
b = self.open_branch()
283
def get_branch_reference(self):
284
"""See BzrDir.get_branch_reference()."""
285
response = self._get_branch_reference()
286
if response[0] == 'ref':
291
def _get_branch_reference(self):
292
path = self._path_for_remote_call(self._client)
293
medium = self._client._medium
295
('BzrDir.open_branchV3', (2, 1)),
296
('BzrDir.open_branchV2', (1, 13)),
297
('BzrDir.open_branch', None),
299
for verb, required_version in candidate_calls:
300
if required_version and medium._is_remote_before(required_version):
303
response = self._call(verb, path)
304
except errors.UnknownSmartMethod:
305
if required_version is None:
307
medium._remember_remote_is_before(required_version)
310
if verb == 'BzrDir.open_branch':
311
if response[0] != 'ok':
312
raise errors.UnexpectedSmartServerResponse(response)
313
if response[1] != '':
314
return ('ref', response[1])
316
return ('branch', '')
317
if response[0] not in ('ref', 'branch'):
318
raise errors.UnexpectedSmartServerResponse(response)
321
def _get_tree_branch(self):
322
"""See BzrDir._get_tree_branch()."""
323
return None, self.open_branch()
325
def open_branch(self, name=None, unsupported=False,
326
ignore_fallbacks=False):
328
raise NotImplementedError('unsupported flag support not implemented yet.')
329
if self._next_open_branch_result is not None:
330
# See create_branch for details.
331
result = self._next_open_branch_result
332
self._next_open_branch_result = None
334
response = self._get_branch_reference()
335
if response[0] == 'ref':
336
# a branch reference, use the existing BranchReference logic.
337
format = BranchReferenceFormat()
338
return format.open(self, name=name, _found=True,
339
location=response[1], ignore_fallbacks=ignore_fallbacks)
340
branch_format_name = response[1]
341
if not branch_format_name:
342
branch_format_name = None
343
format = RemoteBranchFormat(network_name=branch_format_name)
344
return RemoteBranch(self, self.find_repository(), format=format,
345
setup_stacking=not ignore_fallbacks, name=name)
347
def _open_repo_v1(self, path):
348
verb = 'BzrDir.find_repository'
349
response = self._call(verb, path)
350
if response[0] != 'ok':
351
raise errors.UnexpectedSmartServerResponse(response)
352
# servers that only support the v1 method don't support external
355
repo = self._real_bzrdir.open_repository()
356
response = response + ('no', repo._format.network_name())
357
return response, repo
359
def _open_repo_v2(self, path):
360
verb = 'BzrDir.find_repositoryV2'
361
response = self._call(verb, path)
362
if response[0] != 'ok':
363
raise errors.UnexpectedSmartServerResponse(response)
365
repo = self._real_bzrdir.open_repository()
366
response = response + (repo._format.network_name(),)
367
return response, repo
369
def _open_repo_v3(self, path):
370
verb = 'BzrDir.find_repositoryV3'
371
medium = self._client._medium
372
if medium._is_remote_before((1, 13)):
373
raise errors.UnknownSmartMethod(verb)
375
response = self._call(verb, path)
376
except errors.UnknownSmartMethod:
377
medium._remember_remote_is_before((1, 13))
379
if response[0] != 'ok':
380
raise errors.UnexpectedSmartServerResponse(response)
381
return response, None
383
def open_repository(self):
384
path = self._path_for_remote_call(self._client)
386
for probe in [self._open_repo_v3, self._open_repo_v2,
389
response, real_repo = probe(path)
391
except errors.UnknownSmartMethod:
394
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
395
if response[0] != 'ok':
396
raise errors.UnexpectedSmartServerResponse(response)
397
if len(response) != 6:
398
raise SmartProtocolError('incorrect response length %s' % (response,))
399
if response[1] == '':
400
# repo is at this dir.
401
format = response_tuple_to_repo_format(response[2:])
402
# Used to support creating a real format instance when needed.
403
format._creating_bzrdir = self
404
remote_repo = RemoteRepository(self, format)
405
format._creating_repo = remote_repo
406
if real_repo is not None:
407
remote_repo._set_real_repository(real_repo)
410
raise errors.NoRepositoryPresent(self)
412
def has_workingtree(self):
413
if self._has_working_tree is None:
415
self._has_working_tree = self._real_bzrdir.has_workingtree()
416
return self._has_working_tree
418
def open_workingtree(self, recommend_upgrade=True):
419
if self.has_workingtree():
420
raise errors.NotLocalUrl(self.root_transport)
422
raise errors.NoWorkingTree(self.root_transport.base)
424
def _path_for_remote_call(self, client):
425
"""Return the path to be used for this bzrdir in a remote call."""
426
return client.remote_path_from_transport(self.root_transport)
428
def get_branch_transport(self, branch_format, name=None):
430
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
432
def get_repository_transport(self, repository_format):
434
return self._real_bzrdir.get_repository_transport(repository_format)
436
def get_workingtree_transport(self, workingtree_format):
438
return self._real_bzrdir.get_workingtree_transport(workingtree_format)
440
def can_convert_format(self):
441
"""Upgrading of remote bzrdirs is not supported yet."""
444
def needs_format_conversion(self, format=None):
445
"""Upgrading of remote bzrdirs is not supported yet."""
447
symbol_versioning.warn(symbol_versioning.deprecated_in((1, 13, 0))
448
% 'needs_format_conversion(format=None)')
451
def clone(self, url, revision_id=None, force_new_repo=False,
452
preserve_stacking=False):
454
return self._real_bzrdir.clone(url, revision_id=revision_id,
455
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
457
def _get_config(self):
458
return RemoteBzrDirConfig(self)
461
class RemoteRepositoryFormat(repository.RepositoryFormat):
462
"""Format for repositories accessed over a _SmartClient.
464
Instances of this repository are represented by RemoteRepository
467
The RemoteRepositoryFormat is parameterized during construction
468
to reflect the capabilities of the real, remote format. Specifically
469
the attributes rich_root_data and supports_tree_reference are set
470
on a per instance basis, and are not set (and should not be) at
473
:ivar _custom_format: If set, a specific concrete repository format that
474
will be used when initializing a repository with this
475
RemoteRepositoryFormat.
476
:ivar _creating_repo: If set, the repository object that this
477
RemoteRepositoryFormat was created for: it can be called into
478
to obtain data like the network name.
481
_matchingbzrdir = RemoteBzrDirFormat()
484
repository.RepositoryFormat.__init__(self)
485
self._custom_format = None
486
self._network_name = None
487
self._creating_bzrdir = None
488
self._supports_chks = None
489
self._supports_external_lookups = None
490
self._supports_tree_reference = None
491
self._rich_root_data = None
494
return "%s(_network_name=%r)" % (self.__class__.__name__,
498
def fast_deltas(self):
500
return self._custom_format.fast_deltas
503
def rich_root_data(self):
504
if self._rich_root_data is None:
506
self._rich_root_data = self._custom_format.rich_root_data
507
return self._rich_root_data
510
def supports_chks(self):
511
if self._supports_chks is None:
513
self._supports_chks = self._custom_format.supports_chks
514
return self._supports_chks
517
def supports_external_lookups(self):
518
if self._supports_external_lookups is None:
520
self._supports_external_lookups = \
521
self._custom_format.supports_external_lookups
522
return self._supports_external_lookups
525
def supports_tree_reference(self):
526
if self._supports_tree_reference is None:
528
self._supports_tree_reference = \
529
self._custom_format.supports_tree_reference
530
return self._supports_tree_reference
532
def _vfs_initialize(self, a_bzrdir, shared):
533
"""Helper for common code in initialize."""
534
if self._custom_format:
535
# Custom format requested
536
result = self._custom_format.initialize(a_bzrdir, shared=shared)
537
elif self._creating_bzrdir is not None:
538
# Use the format that the repository we were created to back
540
prior_repo = self._creating_bzrdir.open_repository()
541
prior_repo._ensure_real()
542
result = prior_repo._real_repository._format.initialize(
543
a_bzrdir, shared=shared)
545
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
546
# support remote initialization.
547
# We delegate to a real object at this point (as RemoteBzrDir
548
# delegate to the repository format which would lead to infinite
549
# recursion if we just called a_bzrdir.create_repository.
550
a_bzrdir._ensure_real()
551
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
552
if not isinstance(result, RemoteRepository):
553
return self.open(a_bzrdir)
557
def initialize(self, a_bzrdir, shared=False):
558
# Being asked to create on a non RemoteBzrDir:
559
if not isinstance(a_bzrdir, RemoteBzrDir):
560
return self._vfs_initialize(a_bzrdir, shared)
561
medium = a_bzrdir._client._medium
562
if medium._is_remote_before((1, 13)):
563
return self._vfs_initialize(a_bzrdir, shared)
564
# Creating on a remote bzr dir.
565
# 1) get the network name to use.
566
if self._custom_format:
567
network_name = self._custom_format.network_name()
568
elif self._network_name:
569
network_name = self._network_name
571
# Select the current bzrlib default and ask for that.
572
reference_bzrdir_format = bzrdir.format_registry.get('default')()
573
reference_format = reference_bzrdir_format.repository_format
574
network_name = reference_format.network_name()
575
# 2) try direct creation via RPC
576
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
577
verb = 'BzrDir.create_repository'
583
response = a_bzrdir._call(verb, path, network_name, shared_str)
584
except errors.UnknownSmartMethod:
585
# Fallback - use vfs methods
586
medium._remember_remote_is_before((1, 13))
587
return self._vfs_initialize(a_bzrdir, shared)
589
# Turn the response into a RemoteRepository object.
590
format = response_tuple_to_repo_format(response[1:])
591
# Used to support creating a real format instance when needed.
592
format._creating_bzrdir = a_bzrdir
593
remote_repo = RemoteRepository(a_bzrdir, format)
594
format._creating_repo = remote_repo
597
def open(self, a_bzrdir):
598
if not isinstance(a_bzrdir, RemoteBzrDir):
599
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
600
return a_bzrdir.open_repository()
602
def _ensure_real(self):
603
if self._custom_format is None:
604
self._custom_format = repository.network_format_registry.get(
608
def _fetch_order(self):
610
return self._custom_format._fetch_order
613
def _fetch_uses_deltas(self):
615
return self._custom_format._fetch_uses_deltas
618
def _fetch_reconcile(self):
620
return self._custom_format._fetch_reconcile
622
def get_format_description(self):
624
return 'Remote: ' + self._custom_format.get_format_description()
626
def __eq__(self, other):
627
return self.__class__ is other.__class__
629
def network_name(self):
630
if self._network_name:
631
return self._network_name
632
self._creating_repo._ensure_real()
633
return self._creating_repo._real_repository._format.network_name()
636
def pack_compresses(self):
638
return self._custom_format.pack_compresses
641
def _serializer(self):
643
return self._custom_format._serializer
646
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
647
bzrdir.ControlComponent):
648
"""Repository accessed over rpc.
650
For the moment most operations are performed using local transport-backed
654
def __init__(self, remote_bzrdir, format, real_repository=None, _client=None):
655
"""Create a RemoteRepository instance.
657
:param remote_bzrdir: The bzrdir hosting this repository.
658
:param format: The RemoteFormat object to use.
659
:param real_repository: If not None, a local implementation of the
660
repository logic for the repository, usually accessing the data
662
:param _client: Private testing parameter - override the smart client
663
to be used by the repository.
666
self._real_repository = real_repository
668
self._real_repository = None
669
self.bzrdir = remote_bzrdir
671
self._client = remote_bzrdir._client
673
self._client = _client
674
self._format = format
675
self._lock_mode = None
676
self._lock_token = None
678
self._leave_lock = False
679
# Cache of revision parents; misses are cached during read locks, and
680
# write locks when no _real_repository has been set.
681
self._unstacked_provider = graph.CachingParentsProvider(
682
get_parent_map=self._get_parent_map_rpc)
683
self._unstacked_provider.disable_cache()
685
# These depend on the actual remote format, so force them off for
686
# maximum compatibility. XXX: In future these should depend on the
687
# remote repository instance, but this is irrelevant until we perform
688
# reconcile via an RPC call.
689
self._reconcile_does_inventory_gc = False
690
self._reconcile_fixes_text_parents = False
691
self._reconcile_backsup_inventory = False
692
self.base = self.bzrdir.transport.base
693
# Additional places to query for data.
694
self._fallback_repositories = []
697
def user_transport(self):
698
return self.bzrdir.user_transport
701
def control_transport(self):
702
# XXX: Normally you shouldn't directly get at the remote repository
703
# transport, but I'm not sure it's worth making this method
704
# optional -- mbp 2010-04-21
705
return self.bzrdir.get_repository_transport(None)
708
return "%s(%s)" % (self.__class__.__name__, self.base)
712
def abort_write_group(self, suppress_errors=False):
713
"""Complete a write group on the decorated repository.
715
Smart methods perform operations in a single step so this API
716
is not really applicable except as a compatibility thunk
717
for older plugins that don't use e.g. the CommitBuilder
720
:param suppress_errors: see Repository.abort_write_group.
723
return self._real_repository.abort_write_group(
724
suppress_errors=suppress_errors)
728
"""Decorate the real repository for now.
730
In the long term a full blown network facility is needed to avoid
731
creating a real repository object locally.
734
return self._real_repository.chk_bytes
736
def commit_write_group(self):
737
"""Complete a write group on the decorated repository.
739
Smart methods perform operations in a single step so this API
740
is not really applicable except as a compatibility thunk
741
for older plugins that don't use e.g. the CommitBuilder
745
return self._real_repository.commit_write_group()
747
def resume_write_group(self, tokens):
749
return self._real_repository.resume_write_group(tokens)
751
def suspend_write_group(self):
753
return self._real_repository.suspend_write_group()
755
def get_missing_parent_inventories(self, check_for_missing_texts=True):
757
return self._real_repository.get_missing_parent_inventories(
758
check_for_missing_texts=check_for_missing_texts)
760
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
762
return self._real_repository.get_rev_id_for_revno(
765
def get_rev_id_for_revno(self, revno, known_pair):
766
"""See Repository.get_rev_id_for_revno."""
767
path = self.bzrdir._path_for_remote_call(self._client)
769
if self._client._medium._is_remote_before((1, 17)):
770
return self._get_rev_id_for_revno_vfs(revno, known_pair)
771
response = self._call(
772
'Repository.get_rev_id_for_revno', path, revno, known_pair)
773
except errors.UnknownSmartMethod:
774
self._client._medium._remember_remote_is_before((1, 17))
775
return self._get_rev_id_for_revno_vfs(revno, known_pair)
776
if response[0] == 'ok':
777
return True, response[1]
778
elif response[0] == 'history-incomplete':
779
known_pair = response[1:3]
780
for fallback in self._fallback_repositories:
781
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
786
# Not found in any fallbacks
787
return False, known_pair
789
raise errors.UnexpectedSmartServerResponse(response)
791
def _ensure_real(self):
792
"""Ensure that there is a _real_repository set.
794
Used before calls to self._real_repository.
796
Note that _ensure_real causes many roundtrips to the server which are
797
not desirable, and prevents the use of smart one-roundtrip RPC's to
798
perform complex operations (such as accessing parent data, streaming
799
revisions etc). Adding calls to _ensure_real should only be done when
800
bringing up new functionality, adding fallbacks for smart methods that
801
require a fallback path, and never to replace an existing smart method
802
invocation. If in doubt chat to the bzr network team.
804
if self._real_repository is None:
805
if 'hpssvfs' in debug.debug_flags:
807
warning('VFS Repository access triggered\n%s',
808
''.join(traceback.format_stack()))
809
self._unstacked_provider.missing_keys.clear()
810
self.bzrdir._ensure_real()
811
self._set_real_repository(
812
self.bzrdir._real_bzrdir.open_repository())
814
def _translate_error(self, err, **context):
815
self.bzrdir._translate_error(err, repository=self, **context)
817
def find_text_key_references(self):
818
"""Find the text key references within the repository.
820
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
821
revision_ids. Each altered file-ids has the exact revision_ids that
822
altered it listed explicitly.
823
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
824
to whether they were referred to by the inventory of the
825
revision_id that they contain. The inventory texts from all present
826
revision ids are assessed to generate this report.
829
return self._real_repository.find_text_key_references()
831
def _generate_text_key_index(self):
832
"""Generate a new text key index for the repository.
834
This is an expensive function that will take considerable time to run.
836
:return: A dict mapping (file_id, revision_id) tuples to a list of
837
parents, also (file_id, revision_id) tuples.
840
return self._real_repository._generate_text_key_index()
842
def _get_revision_graph(self, revision_id):
843
"""Private method for using with old (< 1.2) servers to fallback."""
844
if revision_id is None:
846
elif revision.is_null(revision_id):
849
path = self.bzrdir._path_for_remote_call(self._client)
850
response = self._call_expecting_body(
851
'Repository.get_revision_graph', path, revision_id)
852
response_tuple, response_handler = response
853
if response_tuple[0] != 'ok':
854
raise errors.UnexpectedSmartServerResponse(response_tuple)
855
coded = response_handler.read_body_bytes()
857
# no revisions in this repository!
859
lines = coded.split('\n')
862
d = tuple(line.split())
863
revision_graph[d[0]] = d[1:]
865
return revision_graph
868
"""See Repository._get_sink()."""
869
return RemoteStreamSink(self)
871
def _get_source(self, to_format):
872
"""Return a source for streaming from this repository."""
873
return RemoteStreamSource(self, to_format)
876
def has_revision(self, revision_id):
877
"""True if this repository has a copy of the revision."""
878
# Copy of bzrlib.repository.Repository.has_revision
879
return revision_id in self.has_revisions((revision_id,))
882
def has_revisions(self, revision_ids):
883
"""Probe to find out the presence of multiple revisions.
885
:param revision_ids: An iterable of revision_ids.
886
:return: A set of the revision_ids that were present.
888
# Copy of bzrlib.repository.Repository.has_revisions
889
parent_map = self.get_parent_map(revision_ids)
890
result = set(parent_map)
891
if _mod_revision.NULL_REVISION in revision_ids:
892
result.add(_mod_revision.NULL_REVISION)
895
def _has_same_fallbacks(self, other_repo):
896
"""Returns true if the repositories have the same fallbacks."""
897
# XXX: copied from Repository; it should be unified into a base class
898
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
899
my_fb = self._fallback_repositories
900
other_fb = other_repo._fallback_repositories
901
if len(my_fb) != len(other_fb):
903
for f, g in zip(my_fb, other_fb):
904
if not f.has_same_location(g):
908
def has_same_location(self, other):
909
# TODO: Move to RepositoryBase and unify with the regular Repository
910
# one; unfortunately the tests rely on slightly different behaviour at
911
# present -- mbp 20090710
912
return (self.__class__ is other.__class__ and
913
self.bzrdir.transport.base == other.bzrdir.transport.base)
915
def get_graph(self, other_repository=None):
916
"""Return the graph for this repository format"""
917
parents_provider = self._make_parents_provider(other_repository)
918
return graph.Graph(parents_provider)
921
def get_known_graph_ancestry(self, revision_ids):
922
"""Return the known graph for a set of revision ids and their ancestors.
924
st = static_tuple.StaticTuple
925
revision_keys = [st(r_id).intern() for r_id in revision_ids]
926
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
927
return graph.GraphThunkIdsToKeys(known_graph)
929
def gather_stats(self, revid=None, committers=None):
930
"""See Repository.gather_stats()."""
931
path = self.bzrdir._path_for_remote_call(self._client)
932
# revid can be None to indicate no revisions, not just NULL_REVISION
933
if revid is None or revision.is_null(revid):
937
if committers is None or not committers:
938
fmt_committers = 'no'
940
fmt_committers = 'yes'
941
response_tuple, response_handler = self._call_expecting_body(
942
'Repository.gather_stats', path, fmt_revid, fmt_committers)
943
if response_tuple[0] != 'ok':
944
raise errors.UnexpectedSmartServerResponse(response_tuple)
946
body = response_handler.read_body_bytes()
948
for line in body.split('\n'):
951
key, val_text = line.split(':')
952
if key in ('revisions', 'size', 'committers'):
953
result[key] = int(val_text)
954
elif key in ('firstrev', 'latestrev'):
955
values = val_text.split(' ')[1:]
956
result[key] = (float(values[0]), long(values[1]))
960
def find_branches(self, using=False):
961
"""See Repository.find_branches()."""
962
# should be an API call to the server.
964
return self._real_repository.find_branches(using=using)
966
def get_physical_lock_status(self):
967
"""See Repository.get_physical_lock_status()."""
968
# should be an API call to the server.
970
return self._real_repository.get_physical_lock_status()
972
def is_in_write_group(self):
973
"""Return True if there is an open write group.
975
write groups are only applicable locally for the smart server..
977
if self._real_repository:
978
return self._real_repository.is_in_write_group()
981
return self._lock_count >= 1
984
"""See Repository.is_shared()."""
985
path = self.bzrdir._path_for_remote_call(self._client)
986
response = self._call('Repository.is_shared', path)
987
if response[0] not in ('yes', 'no'):
988
raise SmartProtocolError('unexpected response code %s' % (response,))
989
return response[0] == 'yes'
991
def is_write_locked(self):
992
return self._lock_mode == 'w'
994
def _warn_if_deprecated(self, branch=None):
995
# If we have a real repository, the check will be done there, if we
996
# don't the check will be done remotely.
1000
# wrong eventually - want a local lock cache context
1001
if not self._lock_mode:
1002
self._note_lock('r')
1003
self._lock_mode = 'r'
1004
self._lock_count = 1
1005
self._unstacked_provider.enable_cache(cache_misses=True)
1006
if self._real_repository is not None:
1007
self._real_repository.lock_read()
1008
for repo in self._fallback_repositories:
1011
self._lock_count += 1
1013
def _remote_lock_write(self, token):
1014
path = self.bzrdir._path_for_remote_call(self._client)
1017
err_context = {'token': token}
1018
response = self._call('Repository.lock_write', path, token,
1020
if response[0] == 'ok':
1021
ok, token = response
1024
raise errors.UnexpectedSmartServerResponse(response)
1026
def lock_write(self, token=None, _skip_rpc=False):
1027
if not self._lock_mode:
1028
self._note_lock('w')
1030
if self._lock_token is not None:
1031
if token != self._lock_token:
1032
raise errors.TokenMismatch(token, self._lock_token)
1033
self._lock_token = token
1035
self._lock_token = self._remote_lock_write(token)
1036
# if self._lock_token is None, then this is something like packs or
1037
# svn where we don't get to lock the repo, or a weave style repository
1038
# where we cannot lock it over the wire and attempts to do so will
1040
if self._real_repository is not None:
1041
self._real_repository.lock_write(token=self._lock_token)
1042
if token is not None:
1043
self._leave_lock = True
1045
self._leave_lock = False
1046
self._lock_mode = 'w'
1047
self._lock_count = 1
1048
cache_misses = self._real_repository is None
1049
self._unstacked_provider.enable_cache(cache_misses=cache_misses)
1050
for repo in self._fallback_repositories:
1051
# Writes don't affect fallback repos
1053
elif self._lock_mode == 'r':
1054
raise errors.ReadOnlyError(self)
1056
self._lock_count += 1
1057
return self._lock_token or None
1059
def leave_lock_in_place(self):
1060
if not self._lock_token:
1061
raise NotImplementedError(self.leave_lock_in_place)
1062
self._leave_lock = True
1064
def dont_leave_lock_in_place(self):
1065
if not self._lock_token:
1066
raise NotImplementedError(self.dont_leave_lock_in_place)
1067
self._leave_lock = False
1069
def _set_real_repository(self, repository):
1070
"""Set the _real_repository for this repository.
1072
:param repository: The repository to fallback to for non-hpss
1073
implemented operations.
1075
if self._real_repository is not None:
1076
# Replacing an already set real repository.
1077
# We cannot do this [currently] if the repository is locked -
1078
# synchronised state might be lost.
1079
if self.is_locked():
1080
raise AssertionError('_real_repository is already set')
1081
if isinstance(repository, RemoteRepository):
1082
raise AssertionError()
1083
self._real_repository = repository
1084
# three code paths happen here:
1085
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1086
# up stacking. In this case self._fallback_repositories is [], and the
1087
# real repo is already setup. Preserve the real repo and
1088
# RemoteRepository.add_fallback_repository will avoid adding
1090
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1091
# ensure_real is triggered from a branch, the real repository to
1092
# set already has a matching list with separate instances, but
1093
# as they are also RemoteRepositories we don't worry about making the
1094
# lists be identical.
1095
# 3) new servers, RemoteRepository.ensure_real is triggered before
1096
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1097
# and need to populate it.
1098
if (self._fallback_repositories and
1099
len(self._real_repository._fallback_repositories) !=
1100
len(self._fallback_repositories)):
1101
if len(self._real_repository._fallback_repositories):
1102
raise AssertionError(
1103
"cannot cleanly remove existing _fallback_repositories")
1104
for fb in self._fallback_repositories:
1105
self._real_repository.add_fallback_repository(fb)
1106
if self._lock_mode == 'w':
1107
# if we are already locked, the real repository must be able to
1108
# acquire the lock with our token.
1109
self._real_repository.lock_write(self._lock_token)
1110
elif self._lock_mode == 'r':
1111
self._real_repository.lock_read()
1113
def start_write_group(self):
1114
"""Start a write group on the decorated repository.
1116
Smart methods perform operations in a single step so this API
1117
is not really applicable except as a compatibility thunk
1118
for older plugins that don't use e.g. the CommitBuilder
1122
return self._real_repository.start_write_group()
1124
def _unlock(self, token):
1125
path = self.bzrdir._path_for_remote_call(self._client)
1127
# with no token the remote repository is not persistently locked.
1129
err_context = {'token': token}
1130
response = self._call('Repository.unlock', path, token,
1132
if response == ('ok',):
1135
raise errors.UnexpectedSmartServerResponse(response)
1137
@only_raises(errors.LockNotHeld, errors.LockBroken)
1139
if not self._lock_count:
1140
return lock.cant_unlock_not_held(self)
1141
self._lock_count -= 1
1142
if self._lock_count > 0:
1144
self._unstacked_provider.disable_cache()
1145
old_mode = self._lock_mode
1146
self._lock_mode = None
1148
# The real repository is responsible at present for raising an
1149
# exception if it's in an unfinished write group. However, it
1150
# normally will *not* actually remove the lock from disk - that's
1151
# done by the server on receiving the Repository.unlock call.
1152
# This is just to let the _real_repository stay up to date.
1153
if self._real_repository is not None:
1154
self._real_repository.unlock()
1156
# The rpc-level lock should be released even if there was a
1157
# problem releasing the vfs-based lock.
1159
# Only write-locked repositories need to make a remote method
1160
# call to perform the unlock.
1161
old_token = self._lock_token
1162
self._lock_token = None
1163
if not self._leave_lock:
1164
self._unlock(old_token)
1165
# Fallbacks are always 'lock_read()' so we don't pay attention to
1167
for repo in self._fallback_repositories:
1170
def break_lock(self):
1171
# should hand off to the network
1173
return self._real_repository.break_lock()
1175
def _get_tarball(self, compression):
1176
"""Return a TemporaryFile containing a repository tarball.
1178
Returns None if the server does not support sending tarballs.
1181
path = self.bzrdir._path_for_remote_call(self._client)
1183
response, protocol = self._call_expecting_body(
1184
'Repository.tarball', path, compression)
1185
except errors.UnknownSmartMethod:
1186
protocol.cancel_read_body()
1188
if response[0] == 'ok':
1189
# Extract the tarball and return it
1190
t = tempfile.NamedTemporaryFile()
1191
# TODO: rpc layer should read directly into it...
1192
t.write(protocol.read_body_bytes())
1195
raise errors.UnexpectedSmartServerResponse(response)
1197
def sprout(self, to_bzrdir, revision_id=None):
1198
# TODO: Option to control what format is created?
1200
dest_repo = self._real_repository._format.initialize(to_bzrdir,
1202
dest_repo.fetch(self, revision_id=revision_id)
1205
### These methods are just thin shims to the VFS object for now.
1207
def revision_tree(self, revision_id):
1209
return self._real_repository.revision_tree(revision_id)
1211
def get_serializer_format(self):
1213
return self._real_repository.get_serializer_format()
1215
def get_commit_builder(self, branch, parents, config, timestamp=None,
1216
timezone=None, committer=None, revprops=None,
1218
# FIXME: It ought to be possible to call this without immediately
1219
# triggering _ensure_real. For now it's the easiest thing to do.
1221
real_repo = self._real_repository
1222
builder = real_repo.get_commit_builder(branch, parents,
1223
config, timestamp=timestamp, timezone=timezone,
1224
committer=committer, revprops=revprops, revision_id=revision_id)
1227
def add_fallback_repository(self, repository):
1228
"""Add a repository to use for looking up data not held locally.
1230
:param repository: A repository.
1232
if not self._format.supports_external_lookups:
1233
raise errors.UnstackableRepositoryFormat(
1234
self._format.network_name(), self.base)
1235
# We need to accumulate additional repositories here, to pass them in
1238
if self.is_locked():
1239
# We will call fallback.unlock() when we transition to the unlocked
1240
# state, so always add a lock here. If a caller passes us a locked
1241
# repository, they are responsible for unlocking it later.
1242
repository.lock_read()
1243
self._check_fallback_repository(repository)
1244
self._fallback_repositories.append(repository)
1245
# If self._real_repository was parameterised already (e.g. because a
1246
# _real_branch had its get_stacked_on_url method called), then the
1247
# repository to be added may already be in the _real_repositories list.
1248
if self._real_repository is not None:
1249
fallback_locations = [repo.user_url for repo in
1250
self._real_repository._fallback_repositories]
1251
if repository.user_url not in fallback_locations:
1252
self._real_repository.add_fallback_repository(repository)
1254
def _check_fallback_repository(self, repository):
1255
"""Check that this repository can fallback to repository safely.
1257
Raise an error if not.
1259
:param repository: A repository to fallback to.
1261
return _mod_repository.InterRepository._assert_same_model(
1264
def add_inventory(self, revid, inv, parents):
1266
return self._real_repository.add_inventory(revid, inv, parents)
1268
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1269
parents, basis_inv=None, propagate_caches=False):
1271
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1272
delta, new_revision_id, parents, basis_inv=basis_inv,
1273
propagate_caches=propagate_caches)
1275
def add_revision(self, rev_id, rev, inv=None, config=None):
1277
return self._real_repository.add_revision(
1278
rev_id, rev, inv=inv, config=config)
1281
def get_inventory(self, revision_id):
1283
return self._real_repository.get_inventory(revision_id)
1285
def iter_inventories(self, revision_ids, ordering=None):
1287
return self._real_repository.iter_inventories(revision_ids, ordering)
1290
def get_revision(self, revision_id):
1292
return self._real_repository.get_revision(revision_id)
1294
def get_transaction(self):
1296
return self._real_repository.get_transaction()
1299
def clone(self, a_bzrdir, revision_id=None):
1301
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1303
def make_working_trees(self):
1304
"""See Repository.make_working_trees"""
1306
return self._real_repository.make_working_trees()
1308
def refresh_data(self):
1309
"""Re-read any data needed to to synchronise with disk.
1311
This method is intended to be called after another repository instance
1312
(such as one used by a smart server) has inserted data into the
1313
repository. It may not be called during a write group, but may be
1314
called at any other time.
1316
if self.is_in_write_group():
1317
raise errors.InternalBzrError(
1318
"May not refresh_data while in a write group.")
1319
if self._real_repository is not None:
1320
self._real_repository.refresh_data()
1322
def revision_ids_to_search_result(self, result_set):
1323
"""Convert a set of revision ids to a graph SearchResult."""
1324
result_parents = set()
1325
for parents in self.get_graph().get_parent_map(
1326
result_set).itervalues():
1327
result_parents.update(parents)
1328
included_keys = result_set.intersection(result_parents)
1329
start_keys = result_set.difference(included_keys)
1330
exclude_keys = result_parents.difference(result_set)
1331
result = graph.SearchResult(start_keys, exclude_keys,
1332
len(result_set), result_set)
1336
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1337
"""Return the revision ids that other has that this does not.
1339
These are returned in topological order.
1341
revision_id: only return revision ids included by revision_id.
1343
return repository.InterRepository.get(
1344
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1346
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1348
# No base implementation to use as RemoteRepository is not a subclass
1349
# of Repository; so this is a copy of Repository.fetch().
1350
if fetch_spec is not None and revision_id is not None:
1351
raise AssertionError(
1352
"fetch_spec and revision_id are mutually exclusive.")
1353
if self.is_in_write_group():
1354
raise errors.InternalBzrError(
1355
"May not fetch while in a write group.")
1356
# fast path same-url fetch operations
1357
if (self.has_same_location(source)
1358
and fetch_spec is None
1359
and self._has_same_fallbacks(source)):
1360
# check that last_revision is in 'from' and then return a
1362
if (revision_id is not None and
1363
not revision.is_null(revision_id)):
1364
self.get_revision(revision_id)
1366
# if there is no specific appropriate InterRepository, this will get
1367
# the InterRepository base class, which raises an
1368
# IncompatibleRepositories when asked to fetch.
1369
inter = repository.InterRepository.get(source, self)
1370
return inter.fetch(revision_id=revision_id, pb=pb,
1371
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1373
def create_bundle(self, target, base, fileobj, format=None):
1375
self._real_repository.create_bundle(target, base, fileobj, format)
1378
def get_ancestry(self, revision_id, topo_sorted=True):
1380
return self._real_repository.get_ancestry(revision_id, topo_sorted)
1382
def fileids_altered_by_revision_ids(self, revision_ids):
1384
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1386
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1388
return self._real_repository._get_versioned_file_checker(
1389
revisions, revision_versions_cache)
1391
def iter_files_bytes(self, desired_files):
1392
"""See Repository.iter_file_bytes.
1395
return self._real_repository.iter_files_bytes(desired_files)
1397
def get_parent_map(self, revision_ids):
1398
"""See bzrlib.Graph.get_parent_map()."""
1399
return self._make_parents_provider().get_parent_map(revision_ids)
1401
def _get_parent_map_rpc(self, keys):
1402
"""Helper for get_parent_map that performs the RPC."""
1403
medium = self._client._medium
1404
if medium._is_remote_before((1, 2)):
1405
# We already found out that the server can't understand
1406
# Repository.get_parent_map requests, so just fetch the whole
1409
# Note that this reads the whole graph, when only some keys are
1410
# wanted. On this old server there's no way (?) to get them all
1411
# in one go, and the user probably will have seen a warning about
1412
# the server being old anyhow.
1413
rg = self._get_revision_graph(None)
1414
# There is an API discrepancy between get_parent_map and
1415
# get_revision_graph. Specifically, a "key:()" pair in
1416
# get_revision_graph just means a node has no parents. For
1417
# "get_parent_map" it means the node is a ghost. So fix up the
1418
# graph to correct this.
1419
# https://bugs.launchpad.net/bzr/+bug/214894
1420
# There is one other "bug" which is that ghosts in
1421
# get_revision_graph() are not returned at all. But we won't worry
1422
# about that for now.
1423
for node_id, parent_ids in rg.iteritems():
1424
if parent_ids == ():
1425
rg[node_id] = (NULL_REVISION,)
1426
rg[NULL_REVISION] = ()
1431
raise ValueError('get_parent_map(None) is not valid')
1432
if NULL_REVISION in keys:
1433
keys.discard(NULL_REVISION)
1434
found_parents = {NULL_REVISION:()}
1436
return found_parents
1439
# TODO(Needs analysis): We could assume that the keys being requested
1440
# from get_parent_map are in a breadth first search, so typically they
1441
# will all be depth N from some common parent, and we don't have to
1442
# have the server iterate from the root parent, but rather from the
1443
# keys we're searching; and just tell the server the keyspace we
1444
# already have; but this may be more traffic again.
1446
# Transform self._parents_map into a search request recipe.
1447
# TODO: Manage this incrementally to avoid covering the same path
1448
# repeatedly. (The server will have to on each request, but the less
1449
# work done the better).
1451
# Negative caching notes:
1452
# new server sends missing when a request including the revid
1453
# 'include-missing:' is present in the request.
1454
# missing keys are serialised as missing:X, and we then call
1455
# provider.note_missing(X) for-all X
1456
parents_map = self._unstacked_provider.get_cached_map()
1457
if parents_map is None:
1458
# Repository is not locked, so there's no cache.
1460
# start_set is all the keys in the cache
1461
start_set = set(parents_map)
1462
# result set is all the references to keys in the cache
1463
result_parents = set()
1464
for parents in parents_map.itervalues():
1465
result_parents.update(parents)
1466
stop_keys = result_parents.difference(start_set)
1467
# We don't need to send ghosts back to the server as a position to
1469
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1470
key_count = len(parents_map)
1471
if (NULL_REVISION in result_parents
1472
and NULL_REVISION in self._unstacked_provider.missing_keys):
1473
# If we pruned NULL_REVISION from the stop_keys because it's also
1474
# in our cache of "missing" keys we need to increment our key count
1475
# by 1, because the reconsitituted SearchResult on the server will
1476
# still consider NULL_REVISION to be an included key.
1478
included_keys = start_set.intersection(result_parents)
1479
start_set.difference_update(included_keys)
1480
recipe = ('manual', start_set, stop_keys, key_count)
1481
body = self._serialise_search_recipe(recipe)
1482
path = self.bzrdir._path_for_remote_call(self._client)
1484
if type(key) is not str:
1486
"key %r not a plain string" % (key,))
1487
verb = 'Repository.get_parent_map'
1488
args = (path, 'include-missing:') + tuple(keys)
1490
response = self._call_with_body_bytes_expecting_body(
1492
except errors.UnknownSmartMethod:
1493
# Server does not support this method, so get the whole graph.
1494
# Worse, we have to force a disconnection, because the server now
1495
# doesn't realise it has a body on the wire to consume, so the
1496
# only way to recover is to abandon the connection.
1498
'Server is too old for fast get_parent_map, reconnecting. '
1499
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1501
# To avoid having to disconnect repeatedly, we keep track of the
1502
# fact the server doesn't understand remote methods added in 1.2.
1503
medium._remember_remote_is_before((1, 2))
1504
# Recurse just once and we should use the fallback code.
1505
return self._get_parent_map_rpc(keys)
1506
response_tuple, response_handler = response
1507
if response_tuple[0] not in ['ok']:
1508
response_handler.cancel_read_body()
1509
raise errors.UnexpectedSmartServerResponse(response_tuple)
1510
if response_tuple[0] == 'ok':
1511
coded = bz2.decompress(response_handler.read_body_bytes())
1513
# no revisions found
1515
lines = coded.split('\n')
1518
d = tuple(line.split())
1520
revision_graph[d[0]] = d[1:]
1523
if d[0].startswith('missing:'):
1525
self._unstacked_provider.note_missing_key(revid)
1527
# no parents - so give the Graph result
1529
revision_graph[d[0]] = (NULL_REVISION,)
1530
return revision_graph
1533
def get_signature_text(self, revision_id):
1535
return self._real_repository.get_signature_text(revision_id)
1538
def _get_inventory_xml(self, revision_id):
1540
return self._real_repository._get_inventory_xml(revision_id)
1542
def reconcile(self, other=None, thorough=False):
1544
return self._real_repository.reconcile(other=other, thorough=thorough)
1546
def all_revision_ids(self):
1548
return self._real_repository.all_revision_ids()
1551
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1553
return self._real_repository.get_deltas_for_revisions(revisions,
1554
specific_fileids=specific_fileids)
1557
def get_revision_delta(self, revision_id, specific_fileids=None):
1559
return self._real_repository.get_revision_delta(revision_id,
1560
specific_fileids=specific_fileids)
1563
def revision_trees(self, revision_ids):
1565
return self._real_repository.revision_trees(revision_ids)
1568
def get_revision_reconcile(self, revision_id):
1570
return self._real_repository.get_revision_reconcile(revision_id)
1573
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
1575
return self._real_repository.check(revision_ids=revision_ids,
1576
callback_refs=callback_refs, check_repo=check_repo)
1578
def copy_content_into(self, destination, revision_id=None):
1580
return self._real_repository.copy_content_into(
1581
destination, revision_id=revision_id)
1583
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
1584
# get a tarball of the remote repository, and copy from that into the
1586
from bzrlib import osutils
1588
# TODO: Maybe a progress bar while streaming the tarball?
1589
note("Copying repository content as tarball...")
1590
tar_file = self._get_tarball('bz2')
1591
if tar_file is None:
1593
destination = to_bzrdir.create_repository()
1595
tar = tarfile.open('repository', fileobj=tar_file,
1597
tmpdir = osutils.mkdtemp()
1599
_extract_tar(tar, tmpdir)
1600
tmp_bzrdir = BzrDir.open(tmpdir)
1601
tmp_repo = tmp_bzrdir.open_repository()
1602
tmp_repo.copy_content_into(destination, revision_id)
1604
osutils.rmtree(tmpdir)
1608
# TODO: Suggestion from john: using external tar is much faster than
1609
# python's tarfile library, but it may not work on windows.
1612
def inventories(self):
1613
"""Decorate the real repository for now.
1615
In the long term a full blown network facility is needed to
1616
avoid creating a real repository object locally.
1619
return self._real_repository.inventories
1622
def pack(self, hint=None, clean_obsolete_packs=False):
1623
"""Compress the data within the repository.
1625
This is not currently implemented within the smart server.
1628
return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1631
def revisions(self):
1632
"""Decorate the real repository for now.
1634
In the short term this should become a real object to intercept graph
1637
In the long term a full blown network facility is needed.
1640
return self._real_repository.revisions
1642
def set_make_working_trees(self, new_value):
1644
new_value_str = "True"
1646
new_value_str = "False"
1647
path = self.bzrdir._path_for_remote_call(self._client)
1649
response = self._call(
1650
'Repository.set_make_working_trees', path, new_value_str)
1651
except errors.UnknownSmartMethod:
1653
self._real_repository.set_make_working_trees(new_value)
1655
if response[0] != 'ok':
1656
raise errors.UnexpectedSmartServerResponse(response)
1659
def signatures(self):
1660
"""Decorate the real repository for now.
1662
In the long term a full blown network facility is needed to avoid
1663
creating a real repository object locally.
1666
return self._real_repository.signatures
1669
def sign_revision(self, revision_id, gpg_strategy):
1671
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1675
"""Decorate the real repository for now.
1677
In the long term a full blown network facility is needed to avoid
1678
creating a real repository object locally.
1681
return self._real_repository.texts
1684
def get_revisions(self, revision_ids):
1686
return self._real_repository.get_revisions(revision_ids)
1688
def supports_rich_root(self):
1689
return self._format.rich_root_data
1691
def iter_reverse_revision_history(self, revision_id):
1693
return self._real_repository.iter_reverse_revision_history(revision_id)
1696
def _serializer(self):
1697
return self._format._serializer
1699
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1701
return self._real_repository.store_revision_signature(
1702
gpg_strategy, plaintext, revision_id)
1704
def add_signature_text(self, revision_id, signature):
1706
return self._real_repository.add_signature_text(revision_id, signature)
1708
def has_signature_for_revision_id(self, revision_id):
1710
return self._real_repository.has_signature_for_revision_id(revision_id)
1712
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1714
return self._real_repository.item_keys_introduced_by(revision_ids,
1715
_files_pb=_files_pb)
1717
def revision_graph_can_have_wrong_parents(self):
1718
# The answer depends on the remote repo format.
1720
return self._real_repository.revision_graph_can_have_wrong_parents()
1722
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1724
return self._real_repository._find_inconsistent_revision_parents(
1727
def _check_for_inconsistent_revision_parents(self):
1729
return self._real_repository._check_for_inconsistent_revision_parents()
1731
def _make_parents_provider(self, other=None):
1732
providers = [self._unstacked_provider]
1733
if other is not None:
1734
providers.insert(0, other)
1735
providers.extend(r._make_parents_provider() for r in
1736
self._fallback_repositories)
1737
return graph.StackedParentsProvider(providers)
1739
def _serialise_search_recipe(self, recipe):
1740
"""Serialise a graph search recipe.
1742
:param recipe: A search recipe (start, stop, count).
1743
:return: Serialised bytes.
1745
start_keys = ' '.join(recipe[1])
1746
stop_keys = ' '.join(recipe[2])
1747
count = str(recipe[3])
1748
return '\n'.join((start_keys, stop_keys, count))
1750
def _serialise_search_result(self, search_result):
1751
if isinstance(search_result, graph.PendingAncestryResult):
1752
parts = ['ancestry-of']
1753
parts.extend(search_result.heads)
1755
recipe = search_result.get_recipe()
1756
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1757
return '\n'.join(parts)
1760
path = self.bzrdir._path_for_remote_call(self._client)
1762
response = self._call('PackRepository.autopack', path)
1763
except errors.UnknownSmartMethod:
1765
self._real_repository._pack_collection.autopack()
1768
if response[0] != 'ok':
1769
raise errors.UnexpectedSmartServerResponse(response)
1772
class RemoteStreamSink(repository.StreamSink):
1774
def _insert_real(self, stream, src_format, resume_tokens):
1775
self.target_repo._ensure_real()
1776
sink = self.target_repo._real_repository._get_sink()
1777
result = sink.insert_stream(stream, src_format, resume_tokens)
1779
self.target_repo.autopack()
1782
def insert_stream(self, stream, src_format, resume_tokens):
1783
target = self.target_repo
1784
target._unstacked_provider.missing_keys.clear()
1785
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1786
if target._lock_token:
1787
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1788
lock_args = (target._lock_token or '',)
1790
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1792
client = target._client
1793
medium = client._medium
1794
path = target.bzrdir._path_for_remote_call(client)
1795
# Probe for the verb to use with an empty stream before sending the
1796
# real stream to it. We do this both to avoid the risk of sending a
1797
# large request that is then rejected, and because we don't want to
1798
# implement a way to buffer, rewind, or restart the stream.
1800
for verb, required_version in candidate_calls:
1801
if medium._is_remote_before(required_version):
1804
# We've already done the probing (and set _is_remote_before) on
1805
# a previous insert.
1808
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1810
response = client.call_with_body_stream(
1811
(verb, path, '') + lock_args, byte_stream)
1812
except errors.UnknownSmartMethod:
1813
medium._remember_remote_is_before(required_version)
1819
return self._insert_real(stream, src_format, resume_tokens)
1820
self._last_inv_record = None
1821
self._last_substream = None
1822
if required_version < (1, 19):
1823
# Remote side doesn't support inventory deltas. Wrap the stream to
1824
# make sure we don't send any. If the stream contains inventory
1825
# deltas we'll interrupt the smart insert_stream request and
1827
stream = self._stop_stream_if_inventory_delta(stream)
1828
byte_stream = smart_repo._stream_to_byte_stream(
1830
resume_tokens = ' '.join(resume_tokens)
1831
response = client.call_with_body_stream(
1832
(verb, path, resume_tokens) + lock_args, byte_stream)
1833
if response[0][0] not in ('ok', 'missing-basis'):
1834
raise errors.UnexpectedSmartServerResponse(response)
1835
if self._last_substream is not None:
1836
# The stream included an inventory-delta record, but the remote
1837
# side isn't new enough to support them. So we need to send the
1838
# rest of the stream via VFS.
1839
self.target_repo.refresh_data()
1840
return self._resume_stream_with_vfs(response, src_format)
1841
if response[0][0] == 'missing-basis':
1842
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1843
resume_tokens = tokens
1844
return resume_tokens, set(missing_keys)
1846
self.target_repo.refresh_data()
1849
def _resume_stream_with_vfs(self, response, src_format):
1850
"""Resume sending a stream via VFS, first resending the record and
1851
substream that couldn't be sent via an insert_stream verb.
1853
if response[0][0] == 'missing-basis':
1854
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1855
# Ignore missing_keys, we haven't finished inserting yet
1858
def resume_substream():
1859
# Yield the substream that was interrupted.
1860
for record in self._last_substream:
1862
self._last_substream = None
1863
def resume_stream():
1864
# Finish sending the interrupted substream
1865
yield ('inventory-deltas', resume_substream())
1866
# Then simply continue sending the rest of the stream.
1867
for substream_kind, substream in self._last_stream:
1868
yield substream_kind, substream
1869
return self._insert_real(resume_stream(), src_format, tokens)
1871
def _stop_stream_if_inventory_delta(self, stream):
1872
"""Normally this just lets the original stream pass-through unchanged.
1874
However if any 'inventory-deltas' substream occurs it will stop
1875
streaming, and store the interrupted substream and stream in
1876
self._last_substream and self._last_stream so that the stream can be
1877
resumed by _resume_stream_with_vfs.
1880
stream_iter = iter(stream)
1881
for substream_kind, substream in stream_iter:
1882
if substream_kind == 'inventory-deltas':
1883
self._last_substream = substream
1884
self._last_stream = stream_iter
1887
yield substream_kind, substream
1890
class RemoteStreamSource(repository.StreamSource):
1891
"""Stream data from a remote server."""
1893
def get_stream(self, search):
1894
if (self.from_repository._fallback_repositories and
1895
self.to_format._fetch_order == 'topological'):
1896
return self._real_stream(self.from_repository, search)
1899
repos = [self.from_repository]
1905
repos.extend(repo._fallback_repositories)
1906
sources.append(repo)
1907
return self.missing_parents_chain(search, sources)
1909
def get_stream_for_missing_keys(self, missing_keys):
1910
self.from_repository._ensure_real()
1911
real_repo = self.from_repository._real_repository
1912
real_source = real_repo._get_source(self.to_format)
1913
return real_source.get_stream_for_missing_keys(missing_keys)
1915
def _real_stream(self, repo, search):
1916
"""Get a stream for search from repo.
1918
This never called RemoteStreamSource.get_stream, and is a heler
1919
for RemoteStreamSource._get_stream to allow getting a stream
1920
reliably whether fallback back because of old servers or trying
1921
to stream from a non-RemoteRepository (which the stacked support
1924
source = repo._get_source(self.to_format)
1925
if isinstance(source, RemoteStreamSource):
1927
source = repo._real_repository._get_source(self.to_format)
1928
return source.get_stream(search)
1930
def _get_stream(self, repo, search):
1931
"""Core worker to get a stream from repo for search.
1933
This is used by both get_stream and the stacking support logic. It
1934
deliberately gets a stream for repo which does not need to be
1935
self.from_repository. In the event that repo is not Remote, or
1936
cannot do a smart stream, a fallback is made to the generic
1937
repository._get_stream() interface, via self._real_stream.
1939
In the event of stacking, streams from _get_stream will not
1940
contain all the data for search - this is normal (see get_stream).
1942
:param repo: A repository.
1943
:param search: A search.
1945
# Fallbacks may be non-smart
1946
if not isinstance(repo, RemoteRepository):
1947
return self._real_stream(repo, search)
1948
client = repo._client
1949
medium = client._medium
1950
path = repo.bzrdir._path_for_remote_call(client)
1951
search_bytes = repo._serialise_search_result(search)
1952
args = (path, self.to_format.network_name())
1954
('Repository.get_stream_1.19', (1, 19)),
1955
('Repository.get_stream', (1, 13))]
1957
for verb, version in candidate_verbs:
1958
if medium._is_remote_before(version):
1961
response = repo._call_with_body_bytes_expecting_body(
1962
verb, args, search_bytes)
1963
except errors.UnknownSmartMethod:
1964
medium._remember_remote_is_before(version)
1966
response_tuple, response_handler = response
1970
return self._real_stream(repo, search)
1971
if response_tuple[0] != 'ok':
1972
raise errors.UnexpectedSmartServerResponse(response_tuple)
1973
byte_stream = response_handler.read_streamed_body()
1974
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1975
if src_format.network_name() != repo._format.network_name():
1976
raise AssertionError(
1977
"Mismatched RemoteRepository and stream src %r, %r" % (
1978
src_format.network_name(), repo._format.network_name()))
1981
def missing_parents_chain(self, search, sources):
1982
"""Chain multiple streams together to handle stacking.
1984
:param search: The overall search to satisfy with streams.
1985
:param sources: A list of Repository objects to query.
1987
self.from_serialiser = self.from_repository._format._serializer
1988
self.seen_revs = set()
1989
self.referenced_revs = set()
1990
# If there are heads in the search, or the key count is > 0, we are not
1992
while not search.is_empty() and len(sources) > 1:
1993
source = sources.pop(0)
1994
stream = self._get_stream(source, search)
1995
for kind, substream in stream:
1996
if kind != 'revisions':
1997
yield kind, substream
1999
yield kind, self.missing_parents_rev_handler(substream)
2000
search = search.refine(self.seen_revs, self.referenced_revs)
2001
self.seen_revs = set()
2002
self.referenced_revs = set()
2003
if not search.is_empty():
2004
for kind, stream in self._get_stream(sources[0], search):
2007
def missing_parents_rev_handler(self, substream):
2008
for content in substream:
2009
revision_bytes = content.get_bytes_as('fulltext')
2010
revision = self.from_serialiser.read_revision_from_string(
2012
self.seen_revs.add(content.key[-1])
2013
self.referenced_revs.update(revision.parent_ids)
2017
class RemoteBranchLockableFiles(LockableFiles):
2018
"""A 'LockableFiles' implementation that talks to a smart server.
2020
This is not a public interface class.
2023
def __init__(self, bzrdir, _client):
2024
self.bzrdir = bzrdir
2025
self._client = _client
2026
self._need_find_modes = True
2027
LockableFiles.__init__(
2028
self, bzrdir.get_branch_transport(None),
2029
'lock', lockdir.LockDir)
2031
def _find_modes(self):
2032
# RemoteBranches don't let the client set the mode of control files.
2033
self._dir_mode = None
2034
self._file_mode = None
2037
class RemoteBranchFormat(branch.BranchFormat):
2039
def __init__(self, network_name=None):
2040
super(RemoteBranchFormat, self).__init__()
2041
self._matchingbzrdir = RemoteBzrDirFormat()
2042
self._matchingbzrdir.set_branch_format(self)
2043
self._custom_format = None
2044
self._network_name = network_name
2046
def __eq__(self, other):
2047
return (isinstance(other, RemoteBranchFormat) and
2048
self.__dict__ == other.__dict__)
2050
def _ensure_real(self):
2051
if self._custom_format is None:
2052
self._custom_format = branch.network_format_registry.get(
2055
def get_format_description(self):
2057
return 'Remote: ' + self._custom_format.get_format_description()
2059
def network_name(self):
2060
return self._network_name
2062
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2063
return a_bzrdir.open_branch(name=name,
2064
ignore_fallbacks=ignore_fallbacks)
2066
def _vfs_initialize(self, a_bzrdir, name):
2067
# Initialisation when using a local bzrdir object, or a non-vfs init
2068
# method is not available on the server.
2069
# self._custom_format is always set - the start of initialize ensures
2071
if isinstance(a_bzrdir, RemoteBzrDir):
2072
a_bzrdir._ensure_real()
2073
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2076
# We assume the bzrdir is parameterised; it may not be.
2077
result = self._custom_format.initialize(a_bzrdir, name)
2078
if (isinstance(a_bzrdir, RemoteBzrDir) and
2079
not isinstance(result, RemoteBranch)):
2080
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2084
def initialize(self, a_bzrdir, name=None):
2085
# 1) get the network name to use.
2086
if self._custom_format:
2087
network_name = self._custom_format.network_name()
2089
# Select the current bzrlib default and ask for that.
2090
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2091
reference_format = reference_bzrdir_format.get_branch_format()
2092
self._custom_format = reference_format
2093
network_name = reference_format.network_name()
2094
# Being asked to create on a non RemoteBzrDir:
2095
if not isinstance(a_bzrdir, RemoteBzrDir):
2096
return self._vfs_initialize(a_bzrdir, name=name)
2097
medium = a_bzrdir._client._medium
2098
if medium._is_remote_before((1, 13)):
2099
return self._vfs_initialize(a_bzrdir, name=name)
2100
# Creating on a remote bzr dir.
2101
# 2) try direct creation via RPC
2102
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2103
if name is not None:
2104
# XXX JRV20100304: Support creating colocated branches
2105
raise errors.NoColocatedBranchSupport(self)
2106
verb = 'BzrDir.create_branch'
2108
response = a_bzrdir._call(verb, path, network_name)
2109
except errors.UnknownSmartMethod:
2110
# Fallback - use vfs methods
2111
medium._remember_remote_is_before((1, 13))
2112
return self._vfs_initialize(a_bzrdir, name=name)
2113
if response[0] != 'ok':
2114
raise errors.UnexpectedSmartServerResponse(response)
2115
# Turn the response into a RemoteRepository object.
2116
format = RemoteBranchFormat(network_name=response[1])
2117
repo_format = response_tuple_to_repo_format(response[3:])
2118
if response[2] == '':
2119
repo_bzrdir = a_bzrdir
2121
repo_bzrdir = RemoteBzrDir(
2122
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2124
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2125
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2126
format=format, setup_stacking=False, name=name)
2127
# XXX: We know this is a new branch, so it must have revno 0, revid
2128
# NULL_REVISION. Creating the branch locked would make this be unable
2129
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2130
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2131
return remote_branch
2133
def make_tags(self, branch):
2135
return self._custom_format.make_tags(branch)
2137
def supports_tags(self):
2138
# Remote branches might support tags, but we won't know until we
2139
# access the real remote branch.
2141
return self._custom_format.supports_tags()
2143
def supports_stacking(self):
2145
return self._custom_format.supports_stacking()
2147
def supports_set_append_revisions_only(self):
2149
return self._custom_format.supports_set_append_revisions_only()
2152
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
2153
"""Branch stored on a server accessed by HPSS RPC.
2155
At the moment most operations are mapped down to simple file operations.
2158
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2159
_client=None, format=None, setup_stacking=True, name=None):
2160
"""Create a RemoteBranch instance.
2162
:param real_branch: An optional local implementation of the branch
2163
format, usually accessing the data via the VFS.
2164
:param _client: Private parameter for testing.
2165
:param format: A RemoteBranchFormat object, None to create one
2166
automatically. If supplied it should have a network_name already
2168
:param setup_stacking: If True make an RPC call to determine the
2169
stacked (or not) status of the branch. If False assume the branch
2171
:param name: Colocated branch name
2173
# We intentionally don't call the parent class's __init__, because it
2174
# will try to assign to self.tags, which is a property in this subclass.
2175
# And the parent's __init__ doesn't do much anyway.
2176
self.bzrdir = remote_bzrdir
2177
if _client is not None:
2178
self._client = _client
2180
self._client = remote_bzrdir._client
2181
self.repository = remote_repository
2182
if real_branch is not None:
2183
self._real_branch = real_branch
2184
# Give the remote repository the matching real repo.
2185
real_repo = self._real_branch.repository
2186
if isinstance(real_repo, RemoteRepository):
2187
real_repo._ensure_real()
2188
real_repo = real_repo._real_repository
2189
self.repository._set_real_repository(real_repo)
2190
# Give the branch the remote repository to let fast-pathing happen.
2191
self._real_branch.repository = self.repository
2193
self._real_branch = None
2194
# Fill out expected attributes of branch for bzrlib API users.
2195
self._clear_cached_state()
2196
# TODO: deprecate self.base in favor of user_url
2197
self.base = self.bzrdir.user_url
2199
self._control_files = None
2200
self._lock_mode = None
2201
self._lock_token = None
2202
self._repo_lock_token = None
2203
self._lock_count = 0
2204
self._leave_lock = False
2205
# Setup a format: note that we cannot call _ensure_real until all the
2206
# attributes above are set: This code cannot be moved higher up in this
2209
self._format = RemoteBranchFormat()
2210
if real_branch is not None:
2211
self._format._network_name = \
2212
self._real_branch._format.network_name()
2214
self._format = format
2215
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2216
# branch.open_branch method.
2217
self._real_ignore_fallbacks = not setup_stacking
2218
if not self._format._network_name:
2219
# Did not get from open_branchV2 - old server.
2221
self._format._network_name = \
2222
self._real_branch._format.network_name()
2223
self.tags = self._format.make_tags(self)
2224
# The base class init is not called, so we duplicate this:
2225
hooks = branch.Branch.hooks['open']
2228
self._is_stacked = False
2230
self._setup_stacking()
2232
def _setup_stacking(self):
2233
# configure stacking into the remote repository, by reading it from
2236
fallback_url = self.get_stacked_on_url()
2237
except (errors.NotStacked, errors.UnstackableBranchFormat,
2238
errors.UnstackableRepositoryFormat), e:
2240
self._is_stacked = True
2241
self._activate_fallback_location(fallback_url)
2243
def _get_config(self):
2244
return RemoteBranchConfig(self)
2246
def _get_real_transport(self):
2247
# if we try vfs access, return the real branch's vfs transport
2249
return self._real_branch._transport
2251
_transport = property(_get_real_transport)
2254
return "%s(%s)" % (self.__class__.__name__, self.base)
2258
def _ensure_real(self):
2259
"""Ensure that there is a _real_branch set.
2261
Used before calls to self._real_branch.
2263
if self._real_branch is None:
2264
if not vfs.vfs_enabled():
2265
raise AssertionError('smart server vfs must be enabled '
2266
'to use vfs implementation')
2267
self.bzrdir._ensure_real()
2268
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2269
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2270
if self.repository._real_repository is None:
2271
# Give the remote repository the matching real repo.
2272
real_repo = self._real_branch.repository
2273
if isinstance(real_repo, RemoteRepository):
2274
real_repo._ensure_real()
2275
real_repo = real_repo._real_repository
2276
self.repository._set_real_repository(real_repo)
2277
# Give the real branch the remote repository to let fast-pathing
2279
self._real_branch.repository = self.repository
2280
if self._lock_mode == 'r':
2281
self._real_branch.lock_read()
2282
elif self._lock_mode == 'w':
2283
self._real_branch.lock_write(token=self._lock_token)
2285
def _translate_error(self, err, **context):
2286
self.repository._translate_error(err, branch=self, **context)
2288
def _clear_cached_state(self):
2289
super(RemoteBranch, self)._clear_cached_state()
2290
if self._real_branch is not None:
2291
self._real_branch._clear_cached_state()
2293
def _clear_cached_state_of_remote_branch_only(self):
2294
"""Like _clear_cached_state, but doesn't clear the cache of
2297
This is useful when falling back to calling a method of
2298
self._real_branch that changes state. In that case the underlying
2299
branch changes, so we need to invalidate this RemoteBranch's cache of
2300
it. However, there's no need to invalidate the _real_branch's cache
2301
too, in fact doing so might harm performance.
2303
super(RemoteBranch, self)._clear_cached_state()
2306
def control_files(self):
2307
# Defer actually creating RemoteBranchLockableFiles until its needed,
2308
# because it triggers an _ensure_real that we otherwise might not need.
2309
if self._control_files is None:
2310
self._control_files = RemoteBranchLockableFiles(
2311
self.bzrdir, self._client)
2312
return self._control_files
2314
def _get_checkout_format(self):
2316
return self._real_branch._get_checkout_format()
2318
def get_physical_lock_status(self):
2319
"""See Branch.get_physical_lock_status()."""
2320
# should be an API call to the server, as branches must be lockable.
2322
return self._real_branch.get_physical_lock_status()
2324
def get_stacked_on_url(self):
2325
"""Get the URL this branch is stacked against.
2327
:raises NotStacked: If the branch is not stacked.
2328
:raises UnstackableBranchFormat: If the branch does not support
2330
:raises UnstackableRepositoryFormat: If the repository does not support
2334
# there may not be a repository yet, so we can't use
2335
# self._translate_error, so we can't use self._call either.
2336
response = self._client.call('Branch.get_stacked_on_url',
2337
self._remote_path())
2338
except errors.ErrorFromSmartServer, err:
2339
# there may not be a repository yet, so we can't call through
2340
# its _translate_error
2341
_translate_error(err, branch=self)
2342
except errors.UnknownSmartMethod, err:
2344
return self._real_branch.get_stacked_on_url()
2345
if response[0] != 'ok':
2346
raise errors.UnexpectedSmartServerResponse(response)
2349
def set_stacked_on_url(self, url):
2350
branch.Branch.set_stacked_on_url(self, url)
2352
self._is_stacked = False
2354
self._is_stacked = True
2356
def _vfs_get_tags_bytes(self):
2358
return self._real_branch._get_tags_bytes()
2360
def _get_tags_bytes(self):
2361
medium = self._client._medium
2362
if medium._is_remote_before((1, 13)):
2363
return self._vfs_get_tags_bytes()
2365
response = self._call('Branch.get_tags_bytes', self._remote_path())
2366
except errors.UnknownSmartMethod:
2367
medium._remember_remote_is_before((1, 13))
2368
return self._vfs_get_tags_bytes()
2371
def _vfs_set_tags_bytes(self, bytes):
2373
return self._real_branch._set_tags_bytes(bytes)
2375
def _set_tags_bytes(self, bytes):
2376
medium = self._client._medium
2377
if medium._is_remote_before((1, 18)):
2378
self._vfs_set_tags_bytes(bytes)
2382
self._remote_path(), self._lock_token, self._repo_lock_token)
2383
response = self._call_with_body_bytes(
2384
'Branch.set_tags_bytes', args, bytes)
2385
except errors.UnknownSmartMethod:
2386
medium._remember_remote_is_before((1, 18))
2387
self._vfs_set_tags_bytes(bytes)
2389
def lock_read(self):
2390
self.repository.lock_read()
2391
if not self._lock_mode:
2392
self._note_lock('r')
2393
self._lock_mode = 'r'
2394
self._lock_count = 1
2395
if self._real_branch is not None:
2396
self._real_branch.lock_read()
2398
self._lock_count += 1
2400
def _remote_lock_write(self, token):
2402
branch_token = repo_token = ''
2404
branch_token = token
2405
repo_token = self.repository.lock_write()
2406
self.repository.unlock()
2407
err_context = {'token': token}
2408
response = self._call(
2409
'Branch.lock_write', self._remote_path(), branch_token,
2410
repo_token or '', **err_context)
2411
if response[0] != 'ok':
2412
raise errors.UnexpectedSmartServerResponse(response)
2413
ok, branch_token, repo_token = response
2414
return branch_token, repo_token
2416
def lock_write(self, token=None):
2417
if not self._lock_mode:
2418
self._note_lock('w')
2419
# Lock the branch and repo in one remote call.
2420
remote_tokens = self._remote_lock_write(token)
2421
self._lock_token, self._repo_lock_token = remote_tokens
2422
if not self._lock_token:
2423
raise SmartProtocolError('Remote server did not return a token!')
2424
# Tell the self.repository object that it is locked.
2425
self.repository.lock_write(
2426
self._repo_lock_token, _skip_rpc=True)
2428
if self._real_branch is not None:
2429
self._real_branch.lock_write(token=self._lock_token)
2430
if token is not None:
2431
self._leave_lock = True
2433
self._leave_lock = False
2434
self._lock_mode = 'w'
2435
self._lock_count = 1
2436
elif self._lock_mode == 'r':
2437
raise errors.ReadOnlyTransaction
2439
if token is not None:
2440
# A token was given to lock_write, and we're relocking, so
2441
# check that the given token actually matches the one we
2443
if token != self._lock_token:
2444
raise errors.TokenMismatch(token, self._lock_token)
2445
self._lock_count += 1
2446
# Re-lock the repository too.
2447
self.repository.lock_write(self._repo_lock_token)
2448
return self._lock_token or None
2450
def _unlock(self, branch_token, repo_token):
2451
err_context = {'token': str((branch_token, repo_token))}
2452
response = self._call(
2453
'Branch.unlock', self._remote_path(), branch_token,
2454
repo_token or '', **err_context)
2455
if response == ('ok',):
2457
raise errors.UnexpectedSmartServerResponse(response)
2459
@only_raises(errors.LockNotHeld, errors.LockBroken)
2462
self._lock_count -= 1
2463
if not self._lock_count:
2464
self._clear_cached_state()
2465
mode = self._lock_mode
2466
self._lock_mode = None
2467
if self._real_branch is not None:
2468
if (not self._leave_lock and mode == 'w' and
2469
self._repo_lock_token):
2470
# If this RemoteBranch will remove the physical lock
2471
# for the repository, make sure the _real_branch
2472
# doesn't do it first. (Because the _real_branch's
2473
# repository is set to be the RemoteRepository.)
2474
self._real_branch.repository.leave_lock_in_place()
2475
self._real_branch.unlock()
2477
# Only write-locked branched need to make a remote method
2478
# call to perform the unlock.
2480
if not self._lock_token:
2481
raise AssertionError('Locked, but no token!')
2482
branch_token = self._lock_token
2483
repo_token = self._repo_lock_token
2484
self._lock_token = None
2485
self._repo_lock_token = None
2486
if not self._leave_lock:
2487
self._unlock(branch_token, repo_token)
2489
self.repository.unlock()
2491
def break_lock(self):
2493
return self._real_branch.break_lock()
2495
def leave_lock_in_place(self):
2496
if not self._lock_token:
2497
raise NotImplementedError(self.leave_lock_in_place)
2498
self._leave_lock = True
2500
def dont_leave_lock_in_place(self):
2501
if not self._lock_token:
2502
raise NotImplementedError(self.dont_leave_lock_in_place)
2503
self._leave_lock = False
2506
def get_rev_id(self, revno, history=None):
2508
return _mod_revision.NULL_REVISION
2509
last_revision_info = self.last_revision_info()
2510
ok, result = self.repository.get_rev_id_for_revno(
2511
revno, last_revision_info)
2514
missing_parent = result[1]
2515
# Either the revision named by the server is missing, or its parent
2516
# is. Call get_parent_map to determine which, so that we report a
2518
parent_map = self.repository.get_parent_map([missing_parent])
2519
if missing_parent in parent_map:
2520
missing_parent = parent_map[missing_parent]
2521
raise errors.RevisionNotPresent(missing_parent, self.repository)
2523
def _last_revision_info(self):
2524
response = self._call('Branch.last_revision_info', self._remote_path())
2525
if response[0] != 'ok':
2526
raise SmartProtocolError('unexpected response code %s' % (response,))
2527
revno = int(response[1])
2528
last_revision = response[2]
2529
return (revno, last_revision)
2531
def _gen_revision_history(self):
2532
"""See Branch._gen_revision_history()."""
2533
if self._is_stacked:
2535
return self._real_branch._gen_revision_history()
2536
response_tuple, response_handler = self._call_expecting_body(
2537
'Branch.revision_history', self._remote_path())
2538
if response_tuple[0] != 'ok':
2539
raise errors.UnexpectedSmartServerResponse(response_tuple)
2540
result = response_handler.read_body_bytes().split('\x00')
2545
def _remote_path(self):
2546
return self.bzrdir._path_for_remote_call(self._client)
2548
def _set_last_revision_descendant(self, revision_id, other_branch,
2549
allow_diverged=False, allow_overwrite_descendant=False):
2550
# This performs additional work to meet the hook contract; while its
2551
# undesirable, we have to synthesise the revno to call the hook, and
2552
# not calling the hook is worse as it means changes can't be prevented.
2553
# Having calculated this though, we can't just call into
2554
# set_last_revision_info as a simple call, because there is a set_rh
2555
# hook that some folk may still be using.
2556
old_revno, old_revid = self.last_revision_info()
2557
history = self._lefthand_history(revision_id)
2558
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2559
err_context = {'other_branch': other_branch}
2560
response = self._call('Branch.set_last_revision_ex',
2561
self._remote_path(), self._lock_token, self._repo_lock_token,
2562
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2564
self._clear_cached_state()
2565
if len(response) != 3 and response[0] != 'ok':
2566
raise errors.UnexpectedSmartServerResponse(response)
2567
new_revno, new_revision_id = response[1:]
2568
self._last_revision_info_cache = new_revno, new_revision_id
2569
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2570
if self._real_branch is not None:
2571
cache = new_revno, new_revision_id
2572
self._real_branch._last_revision_info_cache = cache
2574
def _set_last_revision(self, revision_id):
2575
old_revno, old_revid = self.last_revision_info()
2576
# This performs additional work to meet the hook contract; while its
2577
# undesirable, we have to synthesise the revno to call the hook, and
2578
# not calling the hook is worse as it means changes can't be prevented.
2579
# Having calculated this though, we can't just call into
2580
# set_last_revision_info as a simple call, because there is a set_rh
2581
# hook that some folk may still be using.
2582
history = self._lefthand_history(revision_id)
2583
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2584
self._clear_cached_state()
2585
response = self._call('Branch.set_last_revision',
2586
self._remote_path(), self._lock_token, self._repo_lock_token,
2588
if response != ('ok',):
2589
raise errors.UnexpectedSmartServerResponse(response)
2590
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2593
def set_revision_history(self, rev_history):
2594
# Send just the tip revision of the history; the server will generate
2595
# the full history from that. If the revision doesn't exist in this
2596
# branch, NoSuchRevision will be raised.
2597
if rev_history == []:
2600
rev_id = rev_history[-1]
2601
self._set_last_revision(rev_id)
2602
for hook in branch.Branch.hooks['set_rh']:
2603
hook(self, rev_history)
2604
self._cache_revision_history(rev_history)
2606
def _get_parent_location(self):
2607
medium = self._client._medium
2608
if medium._is_remote_before((1, 13)):
2609
return self._vfs_get_parent_location()
2611
response = self._call('Branch.get_parent', self._remote_path())
2612
except errors.UnknownSmartMethod:
2613
medium._remember_remote_is_before((1, 13))
2614
return self._vfs_get_parent_location()
2615
if len(response) != 1:
2616
raise errors.UnexpectedSmartServerResponse(response)
2617
parent_location = response[0]
2618
if parent_location == '':
2620
return parent_location
2622
def _vfs_get_parent_location(self):
2624
return self._real_branch._get_parent_location()
2626
def _set_parent_location(self, url):
2627
medium = self._client._medium
2628
if medium._is_remote_before((1, 15)):
2629
return self._vfs_set_parent_location(url)
2631
call_url = url or ''
2632
if type(call_url) is not str:
2633
raise AssertionError('url must be a str or None (%s)' % url)
2634
response = self._call('Branch.set_parent_location',
2635
self._remote_path(), self._lock_token, self._repo_lock_token,
2637
except errors.UnknownSmartMethod:
2638
medium._remember_remote_is_before((1, 15))
2639
return self._vfs_set_parent_location(url)
2641
raise errors.UnexpectedSmartServerResponse(response)
2643
def _vfs_set_parent_location(self, url):
2645
return self._real_branch._set_parent_location(url)
2648
def pull(self, source, overwrite=False, stop_revision=None,
2650
self._clear_cached_state_of_remote_branch_only()
2652
return self._real_branch.pull(
2653
source, overwrite=overwrite, stop_revision=stop_revision,
2654
_override_hook_target=self, **kwargs)
2657
def push(self, target, overwrite=False, stop_revision=None):
2659
return self._real_branch.push(
2660
target, overwrite=overwrite, stop_revision=stop_revision,
2661
_override_hook_source_branch=self)
2663
def is_locked(self):
2664
return self._lock_count >= 1
2667
def revision_id_to_revno(self, revision_id):
2669
return self._real_branch.revision_id_to_revno(revision_id)
2672
def set_last_revision_info(self, revno, revision_id):
2673
# XXX: These should be returned by the set_last_revision_info verb
2674
old_revno, old_revid = self.last_revision_info()
2675
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2676
revision_id = ensure_null(revision_id)
2678
response = self._call('Branch.set_last_revision_info',
2679
self._remote_path(), self._lock_token, self._repo_lock_token,
2680
str(revno), revision_id)
2681
except errors.UnknownSmartMethod:
2683
self._clear_cached_state_of_remote_branch_only()
2684
self._real_branch.set_last_revision_info(revno, revision_id)
2685
self._last_revision_info_cache = revno, revision_id
2687
if response == ('ok',):
2688
self._clear_cached_state()
2689
self._last_revision_info_cache = revno, revision_id
2690
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2691
# Update the _real_branch's cache too.
2692
if self._real_branch is not None:
2693
cache = self._last_revision_info_cache
2694
self._real_branch._last_revision_info_cache = cache
2696
raise errors.UnexpectedSmartServerResponse(response)
2699
def generate_revision_history(self, revision_id, last_rev=None,
2701
medium = self._client._medium
2702
if not medium._is_remote_before((1, 6)):
2703
# Use a smart method for 1.6 and above servers
2705
self._set_last_revision_descendant(revision_id, other_branch,
2706
allow_diverged=True, allow_overwrite_descendant=True)
2708
except errors.UnknownSmartMethod:
2709
medium._remember_remote_is_before((1, 6))
2710
self._clear_cached_state_of_remote_branch_only()
2711
self.set_revision_history(self._lefthand_history(revision_id,
2712
last_rev=last_rev,other_branch=other_branch))
2714
def set_push_location(self, location):
2716
return self._real_branch.set_push_location(location)
2719
class RemoteConfig(object):
2720
"""A Config that reads and writes from smart verbs.
2722
It is a low-level object that considers config data to be name/value pairs
2723
that may be associated with a section. Assigning meaning to the these
2724
values is done at higher levels like bzrlib.config.TreeConfig.
2727
def get_option(self, name, section=None, default=None):
2728
"""Return the value associated with a named option.
2730
:param name: The name of the value
2731
:param section: The section the option is in (if any)
2732
:param default: The value to return if the value is not set
2733
:return: The value or default value
2736
configobj = self._get_configobj()
2738
section_obj = configobj
2741
section_obj = configobj[section]
2744
return section_obj.get(name, default)
2745
except errors.UnknownSmartMethod:
2746
return self._vfs_get_option(name, section, default)
2748
def _response_to_configobj(self, response):
2749
if len(response[0]) and response[0][0] != 'ok':
2750
raise errors.UnexpectedSmartServerResponse(response)
2751
lines = response[1].read_body_bytes().splitlines()
2752
return config.ConfigObj(lines, encoding='utf-8')
2755
class RemoteBranchConfig(RemoteConfig):
2756
"""A RemoteConfig for Branches."""
2758
def __init__(self, branch):
2759
self._branch = branch
2761
def _get_configobj(self):
2762
path = self._branch._remote_path()
2763
response = self._branch._client.call_expecting_body(
2764
'Branch.get_config_file', path)
2765
return self._response_to_configobj(response)
2767
def set_option(self, value, name, section=None):
2768
"""Set the value associated with a named option.
2770
:param value: The value to set
2771
:param name: The name of the value to set
2772
:param section: The section the option is in (if any)
2774
medium = self._branch._client._medium
2775
if medium._is_remote_before((1, 14)):
2776
return self._vfs_set_option(value, name, section)
2778
path = self._branch._remote_path()
2779
response = self._branch._client.call('Branch.set_config_option',
2780
path, self._branch._lock_token, self._branch._repo_lock_token,
2781
value.encode('utf8'), name, section or '')
2782
except errors.UnknownSmartMethod:
2783
medium._remember_remote_is_before((1, 14))
2784
return self._vfs_set_option(value, name, section)
2786
raise errors.UnexpectedSmartServerResponse(response)
2788
def _real_object(self):
2789
self._branch._ensure_real()
2790
return self._branch._real_branch
2792
def _vfs_set_option(self, value, name, section=None):
2793
return self._real_object()._get_config().set_option(
2794
value, name, section)
2797
class RemoteBzrDirConfig(RemoteConfig):
2798
"""A RemoteConfig for BzrDirs."""
2800
def __init__(self, bzrdir):
2801
self._bzrdir = bzrdir
2803
def _get_configobj(self):
2804
medium = self._bzrdir._client._medium
2805
verb = 'BzrDir.get_config_file'
2806
if medium._is_remote_before((1, 15)):
2807
raise errors.UnknownSmartMethod(verb)
2808
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2809
response = self._bzrdir._call_expecting_body(
2811
return self._response_to_configobj(response)
2813
def _vfs_get_option(self, name, section, default):
2814
return self._real_object()._get_config().get_option(
2815
name, section, default)
2817
def set_option(self, value, name, section=None):
2818
"""Set the value associated with a named option.
2820
:param value: The value to set
2821
:param name: The name of the value to set
2822
:param section: The section the option is in (if any)
2824
return self._real_object()._get_config().set_option(
2825
value, name, section)
2827
def _real_object(self):
2828
self._bzrdir._ensure_real()
2829
return self._bzrdir._real_bzrdir
2833
def _extract_tar(tar, to_dir):
2834
"""Extract all the contents of a tarfile object.
2836
A replacement for extractall, which is not present in python2.4
2839
tar.extract(tarinfo, to_dir)
2842
def _translate_error(err, **context):
2843
"""Translate an ErrorFromSmartServer into a more useful error.
2845
Possible context keys:
2853
If the error from the server doesn't match a known pattern, then
2854
UnknownErrorFromSmartServer is raised.
2858
return context[name]
2859
except KeyError, key_err:
2860
mutter('Missing key %r in context %r', key_err.args[0], context)
2863
"""Get the path from the context if present, otherwise use first error
2867
return context['path']
2868
except KeyError, key_err:
2870
return err.error_args[0]
2871
except IndexError, idx_err:
2873
'Missing key %r in context %r', key_err.args[0], context)
2876
if err.error_verb == 'IncompatibleRepositories':
2877
raise errors.IncompatibleRepositories(err.error_args[0],
2878
err.error_args[1], err.error_args[2])
2879
elif err.error_verb == 'NoSuchRevision':
2880
raise NoSuchRevision(find('branch'), err.error_args[0])
2881
elif err.error_verb == 'nosuchrevision':
2882
raise NoSuchRevision(find('repository'), err.error_args[0])
2883
elif err.error_verb == 'nobranch':
2884
if len(err.error_args) >= 1:
2885
extra = err.error_args[0]
2888
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2890
elif err.error_verb == 'norepository':
2891
raise errors.NoRepositoryPresent(find('bzrdir'))
2892
elif err.error_verb == 'LockContention':
2893
raise errors.LockContention('(remote lock)')
2894
elif err.error_verb == 'UnlockableTransport':
2895
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2896
elif err.error_verb == 'LockFailed':
2897
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2898
elif err.error_verb == 'TokenMismatch':
2899
raise errors.TokenMismatch(find('token'), '(remote token)')
2900
elif err.error_verb == 'Diverged':
2901
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2902
elif err.error_verb == 'TipChangeRejected':
2903
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2904
elif err.error_verb == 'UnstackableBranchFormat':
2905
raise errors.UnstackableBranchFormat(*err.error_args)
2906
elif err.error_verb == 'UnstackableRepositoryFormat':
2907
raise errors.UnstackableRepositoryFormat(*err.error_args)
2908
elif err.error_verb == 'NotStacked':
2909
raise errors.NotStacked(branch=find('branch'))
2910
elif err.error_verb == 'PermissionDenied':
2912
if len(err.error_args) >= 2:
2913
extra = err.error_args[1]
2916
raise errors.PermissionDenied(path, extra=extra)
2917
elif err.error_verb == 'ReadError':
2919
raise errors.ReadError(path)
2920
elif err.error_verb == 'NoSuchFile':
2922
raise errors.NoSuchFile(path)
2923
elif err.error_verb == 'FileExists':
2924
raise errors.FileExists(err.error_args[0])
2925
elif err.error_verb == 'DirectoryNotEmpty':
2926
raise errors.DirectoryNotEmpty(err.error_args[0])
2927
elif err.error_verb == 'ShortReadvError':
2928
args = err.error_args
2929
raise errors.ShortReadvError(
2930
args[0], int(args[1]), int(args[2]), int(args[3]))
2931
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2932
encoding = str(err.error_args[0]) # encoding must always be a string
2933
val = err.error_args[1]
2934
start = int(err.error_args[2])
2935
end = int(err.error_args[3])
2936
reason = str(err.error_args[4]) # reason must always be a string
2937
if val.startswith('u:'):
2938
val = val[2:].decode('utf-8')
2939
elif val.startswith('s:'):
2940
val = val[2:].decode('base64')
2941
if err.error_verb == 'UnicodeDecodeError':
2942
raise UnicodeDecodeError(encoding, val, start, end, reason)
2943
elif err.error_verb == 'UnicodeEncodeError':
2944
raise UnicodeEncodeError(encoding, val, start, end, reason)
2945
elif err.error_verb == 'ReadOnlyError':
2946
raise errors.TransportNotPossible('readonly transport')
2947
raise errors.UnknownErrorFromSmartServer(err)