13
13
# You should have received a copy of the GNU General Public License
14
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
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
20
from cStringIO import StringIO
19
22
from bzrlib import (
30
repository as _mod_repository,
32
revision as _mod_revision,
36
from bzrlib.branch import BranchReferenceFormat
28
from bzrlib.branch import Branch, BranchReferenceFormat
37
29
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
39
from bzrlib.errors import (
30
from bzrlib.config import BranchConfig, TreeConfig
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
32
from bzrlib.errors import NoSuchRevision
43
33
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]
34
from bzrlib.revision import NULL_REVISION
35
from bzrlib.smart import client, vfs
36
from bzrlib.trace import note
89
38
# Note: RemoteBzrDirFormat is in bzrdir.py
91
class RemoteBzrDir(BzrDir, _RpcHelper):
40
class RemoteBzrDir(BzrDir):
92
41
"""Control directory on a remote server, accessed via bzr:// or similar."""
94
def __init__(self, transport, format, _client=None, _force_probe=False):
43
def __init__(self, transport, _client=None):
95
44
"""Construct a RemoteBzrDir.
97
46
:param _client: Private parameter for testing. Disables probing and the
98
47
use of a real bzrdir.
100
BzrDir.__init__(self, transport, format)
49
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
101
50
# this object holds a delegated bzrdir that uses file-level operations
102
51
# to talk to the other side
103
52
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
109
54
if _client is None:
110
medium = transport.get_smart_medium()
111
self._client = client._SmartClient(medium)
55
self._medium = transport.get_smart_client()
56
self._client = client._SmartClient(self._medium)
113
58
self._client = _client
120
return '%s(%r)' % (self.__class__.__name__, self._client)
122
def _probe_bzrdir(self):
123
medium = self._client._medium
124
62
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)
63
response = self._client.call('BzrDir.open', path)
151
64
if response not in [('yes',), ('no',)]:
152
65
raise errors.UnexpectedSmartServerResponse(response)
153
66
if response == ('no',):
154
raise errors.NotBranchError(path=self.root_transport.base)
67
raise errors.NotBranchError(path=transport.base)
156
69
def _ensure_real(self):
157
70
"""Ensure that there is a _real_bzrdir set.
159
72
Used before calls to self._real_bzrdir.
161
74
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
75
self._real_bzrdir = BzrDir.open_from_transport(
167
76
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
78
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):
80
self._real_bzrdir.create_repository(shared=shared)
81
return self.open_repository()
83
def create_branch(self):
85
real_branch = self._real_bzrdir.create_branch()
86
return RemoteBranch(self, self.find_repository(), real_branch)
88
def create_workingtree(self, revision_id=None):
273
89
raise errors.NotLocalUrl(self.transport.base)
275
91
def find_branch_format(self):
283
99
def get_branch_reference(self):
284
100
"""See BzrDir.get_branch_reference()."""
285
response = self._get_branch_reference()
286
if response[0] == 'ref':
291
def _get_branch_reference(self):
292
101
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'):
102
response = self._client.call('BzrDir.open_branch', path)
103
if response[0] == 'ok':
104
if response[1] == '':
105
# branch at this location.
108
# a branch reference, use the existing BranchReference logic.
110
elif response == ('nobranch',):
111
raise errors.NotBranchError(path=self.root_transport.base)
318
113
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':
115
def open_branch(self, _unsupported=False):
116
assert _unsupported == False, 'unsupported flag support not implemented yet.'
117
reference_url = self.get_branch_reference()
118
if reference_url is None:
119
# branch at this location.
120
return RemoteBranch(self, self.find_repository())
336
122
# a branch reference, use the existing BranchReference logic.
337
123
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
124
return format.open(self, _found=True, location=reference_url)
383
126
def open_repository(self):
384
127
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,))
128
response = self._client.call('BzrDir.find_repository', path)
129
assert response[0] in ('ok', 'norepository'), \
130
'unexpected response code %s' % (response,)
131
if response[0] == 'norepository':
132
raise errors.NoRepositoryPresent(self)
133
assert len(response) == 4, 'incorrect response length %s' % (response,)
399
134
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)
135
format = RemoteRepositoryFormat()
136
format.rich_root_data = (response[2] == 'yes')
137
format.supports_tree_reference = (response[3] == 'yes')
138
return RemoteRepository(self, format)
410
140
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
142
def open_workingtree(self, recommend_upgrade=True):
419
if self.has_workingtree():
144
if self._real_bzrdir.has_workingtree():
420
145
raise errors.NotLocalUrl(self.root_transport)
422
147
raise errors.NoWorkingTree(self.root_transport.base)
464
182
Instances of this repository are represented by RemoteRepository
467
The RemoteRepositoryFormat is parameterized during construction
185
The RemoteRepositoryFormat is parameterised during construction
468
186
to reflect the capabilities of the real, remote format. Specifically
469
187
the attributes rich_root_data and supports_tree_reference are set
470
188
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)
192
_matchingbzrdir = RemoteBzrDirFormat
557
194
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
195
assert isinstance(a_bzrdir, RemoteBzrDir), \
196
'%r is not a RemoteBzrDir' % (a_bzrdir,)
197
return a_bzrdir.create_repository(shared=shared)
597
199
def open(self, a_bzrdir):
598
if not isinstance(a_bzrdir, RemoteBzrDir):
599
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
200
assert isinstance(a_bzrdir, RemoteBzrDir)
600
201
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
203
def get_format_description(self):
624
return 'Remote: ' + self._custom_format.get_format_description()
204
return 'bzr remote repository'
626
206
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):
207
return self.__class__ == other.__class__
209
def check_conversion_target(self, target_format):
210
if self.rich_root_data and not target_format.rich_root_data:
211
raise errors.BadConversionTarget(
212
'Does not support rich root data.', target_format)
213
if (self.supports_tree_reference and
214
not getattr(target_format, 'supports_tree_reference', False)):
215
raise errors.BadConversionTarget(
216
'Does not support nested trees', target_format)
219
class RemoteRepository(object):
648
220
"""Repository accessed over rpc.
650
222
For the moment most operations are performed using local transport-backed
676
248
self._lock_token = None
677
249
self._lock_count = 0
678
250
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
252
def _ensure_real(self):
792
253
"""Ensure that there is a _real_repository set.
794
255
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()
257
if not self._real_repository:
810
258
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."""
259
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
260
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
262
def get_revision_graph(self, revision_id=None):
263
"""See Repository.get_revision_graph()."""
844
264
if revision_id is None:
846
elif revision.is_null(revision_id):
266
elif revision_id == NULL_REVISION:
849
269
path = self.bzrdir._path_for_remote_call(self._client)
850
response = self._call_expecting_body(
270
assert type(revision_id) is str
271
response = self._client.call_expecting_body(
851
272
'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)
273
if response[0][0] not in ['ok', 'nosuchrevision']:
274
raise errors.UnexpectedSmartServerResponse(response[0])
275
if response[0][0] == 'ok':
276
coded = response[1].read_body_bytes()
278
# no revisions in this repository!
280
lines = coded.split('\n')
283
d = list(line.split())
284
revision_graph[d[0]] = d[1:]
286
return revision_graph
288
response_body = response[1].read_body_bytes()
289
assert response_body == ''
290
raise NoSuchRevision(self, revision_id)
876
292
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)
293
"""See Repository.has_revision()."""
294
if revision_id is None:
295
# The null revision is always present.
297
path = self.bzrdir._path_for_remote_call(self._client)
298
response = self._client.call('Repository.has_revision', path, revision_id)
299
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
300
return response[0] == 'yes'
915
302
def get_graph(self, other_repository=None):
916
303
"""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)
304
return self._real_repository.get_graph(other_repository)
929
306
def gather_stats(self, revid=None, committers=None):
930
307
"""See Repository.gather_stats()."""
931
308
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):
309
if revid in (None, NULL_REVISION):
936
312
fmt_revid = revid
1208
470
self._ensure_real()
1209
471
return self._real_repository.revision_tree(revision_id)
1211
def get_serializer_format(self):
1213
return self._real_repository.get_serializer_format()
1215
473
def get_commit_builder(self, branch, parents, config, timestamp=None,
1216
474
timezone=None, committer=None, revprops=None,
1217
475
revision_id=None):
1218
476
# FIXME: It ought to be possible to call this without immediately
1219
477
# triggering _ensure_real. For now it's the easiest thing to do.
1220
478
self._ensure_real()
1221
real_repo = self._real_repository
1222
builder = real_repo.get_commit_builder(branch, parents,
479
builder = self._real_repository.get_commit_builder(branch, parents,
1223
480
config, timestamp=timestamp, timezone=timezone,
1224
481
committer=committer, revprops=revprops, revision_id=revision_id)
482
# Make the builder use this RemoteRepository rather than the real one.
483
builder.repository = self
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
487
def add_inventory(self, revid, inv, parents):
1265
488
self._ensure_real()
1266
489
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
492
def add_revision(self, rev_id, rev, inv=None, config=None):
1276
493
self._ensure_real()
1277
494
return self._real_repository.add_revision(
1301
519
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1303
521
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)
522
"""RemoteRepositories never create working trees by default."""
525
def fetch(self, source, revision_id=None, pb=None):
527
return self._real_repository.fetch(
528
source, revision_id=revision_id, pb=pb)
531
def control_weaves(self):
533
return self._real_repository.control_weaves
1377
535
@needs_read_lock
1378
536
def get_ancestry(self, revision_id, topo_sorted=True):
1379
537
self._ensure_real()
1380
538
return self._real_repository.get_ancestry(revision_id, topo_sorted)
541
def get_inventory_weave(self):
543
return self._real_repository.get_inventory_weave()
1382
545
def fileids_altered_by_revision_ids(self, revision_ids):
1383
546
self._ensure_real()
1384
547
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
1532
549
@needs_read_lock
1533
550
def get_signature_text(self, revision_id):
1534
551
self._ensure_real()
1535
552
return self._real_repository.get_signature_text(revision_id)
1537
554
@needs_read_lock
1538
def _get_inventory_xml(self, revision_id):
1540
return self._real_repository._get_inventory_xml(revision_id)
555
def get_revision_graph_with_ghosts(self, revision_ids=None):
557
return self._real_repository.get_revision_graph_with_ghosts(
558
revision_ids=revision_ids)
561
def get_inventory_xml(self, revision_id):
563
return self._real_repository.get_inventory_xml(revision_id)
565
def deserialise_inventory(self, revision_id, xml):
567
return self._real_repository.deserialise_inventory(revision_id, xml)
1542
569
def reconcile(self, other=None, thorough=False):
1543
570
self._ensure_real()
1544
571
return self._real_repository.reconcile(other=other, thorough=thorough)
1546
573
def all_revision_ids(self):
1547
574
self._ensure_real()
1548
575
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)
578
def get_deltas_for_revisions(self, revisions):
580
return self._real_repository.get_deltas_for_revisions(revisions)
583
def get_revision_delta(self, revision_id):
585
return self._real_repository.get_revision_delta(revision_id)
1562
587
@needs_read_lock
1563
588
def revision_trees(self, revision_ids):
1696
658
def _serializer(self):
1697
return self._format._serializer
660
return self._real_repository._serializer
1699
662
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1700
663
self._ensure_real()
1701
664
return self._real_repository.store_revision_signature(
1702
665
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
667
def has_signature_for_revision_id(self, revision_id):
1709
668
self._ensure_real()
1710
669
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
672
class RemoteBranchLockableFiles(LockableFiles):
2018
673
"""A 'LockableFiles' implementation that talks to a smart server.
2020
675
This is not a public interface class.
2033
688
self._dir_mode = None
2034
689
self._file_mode = None
692
"""'get' a remote path as per the LockableFiles interface.
694
:param path: the file to 'get'. If this is 'branch.conf', we do not
695
just retrieve a file, instead we ask the smart server to generate
696
a configuration for us - which is retrieved as an INI file.
698
if path == 'branch.conf':
699
path = self.bzrdir._path_for_remote_call(self._client)
700
response = self._client.call_expecting_body(
701
'Branch.get_config_file', path)
702
assert response[0][0] == 'ok', \
703
'unexpected response code %s' % (response[0],)
704
return StringIO(response[1].read_body_bytes())
707
return LockableFiles.get(self, path)
2037
710
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
712
def __eq__(self, other):
2047
return (isinstance(other, RemoteBranchFormat) and
713
return (isinstance(other, RemoteBranchFormat) and
2048
714
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
716
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):
717
return 'Remote BZR Branch'
719
def get_format_string(self):
720
return 'Remote BZR Branch'
722
def open(self, a_bzrdir):
723
assert isinstance(a_bzrdir, RemoteBzrDir)
724
return a_bzrdir.open_branch()
726
def initialize(self, a_bzrdir):
727
assert isinstance(a_bzrdir, RemoteBzrDir)
728
return a_bzrdir.create_branch()
731
class RemoteBranch(branch.Branch):
2153
732
"""Branch stored on a server accessed by HPSS RPC.
2155
734
At the moment most operations are mapped down to simple file operations.
2158
737
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2159
_client=None, format=None, setup_stacking=True, name=None):
2160
739
"""Create a RemoteBranch instance.
2162
741
:param real_branch: An optional local implementation of the branch
2163
742
format, usually accessing the data via the VFS.
2164
743
: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
745
# We intentionally don't call the parent class's __init__, because it
2174
746
# will try to assign to self.tags, which is a property in this subclass.
2175
747
# And the parent's __init__ doesn't do much anyway.
748
self._revision_history_cache = None
2176
749
self.bzrdir = remote_bzrdir
2177
750
if _client is not None:
2178
751
self._client = _client
2180
self._client = remote_bzrdir._client
753
self._client = client._SmartClient(self.bzrdir._medium)
2181
754
self.repository = remote_repository
2182
755
if real_branch is not None:
2183
756
self._real_branch = real_branch
2261
784
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')
786
if not self._real_branch:
787
assert vfs.vfs_enabled()
2267
788
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
789
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
790
# Give the remote repository the matching real repo.
791
real_repo = self._real_branch.repository
792
if isinstance(real_repo, RemoteRepository):
793
real_repo._ensure_real()
794
real_repo = real_repo._real_repository
795
self.repository._set_real_repository(real_repo)
796
# Give the branch the remote repository to let fast-pathing happen.
2279
797
self._real_branch.repository = self.repository
798
# XXX: deal with _lock_mode == 'w'
2280
799
if self._lock_mode == 'r':
2281
800
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
803
def control_files(self):
2321
818
self._ensure_real()
2322
819
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
821
def lock_read(self):
2390
self.repository.lock_read()
2391
822
if not self._lock_mode:
2392
self._note_lock('r')
2393
823
self._lock_mode = 'r'
2394
824
self._lock_count = 1
2395
825
if self._real_branch is not None:
2404
834
branch_token = token
2405
835
repo_token = self.repository.lock_write()
2406
836
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':
837
path = self.bzrdir._path_for_remote_call(self._client)
838
response = self._client.call('Branch.lock_write', path, branch_token,
840
if response[0] == 'ok':
841
ok, branch_token, repo_token = response
842
return branch_token, repo_token
843
elif response[0] == 'LockContention':
844
raise errors.LockContention('(remote lock)')
845
elif response[0] == 'TokenMismatch':
846
raise errors.TokenMismatch(token, '(remote token)')
847
elif response[0] == 'UnlockableTransport':
848
raise errors.UnlockableTransport(self.bzrdir.root_transport)
849
elif response[0] == 'ReadOnlyError':
850
raise errors.ReadOnlyError(self)
2412
852
raise errors.UnexpectedSmartServerResponse(response)
2413
ok, branch_token, repo_token = response
2414
return branch_token, repo_token
2416
854
def lock_write(self, token=None):
2417
855
if not self._lock_mode:
2418
self._note_lock('w')
2419
# Lock the branch and repo in one remote call.
2420
856
remote_tokens = self._remote_lock_write(token)
2421
857
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)
858
assert self._lock_token, 'Remote server did not return a token!'
859
# TODO: We really, really, really don't want to call _ensure_real
860
# here, but it's the easiest way to ensure coherency between the
861
# state of the RemoteBranch and RemoteRepository objects and the
862
# physical locks. If we don't materialise the real objects here,
863
# then getting everything in the right state later is complex, so
864
# for now we just do it the lazy way.
865
# -- Andrew Bennetts, 2007-02-22.
2428
867
if self._real_branch is not None:
2429
self._real_branch.lock_write(token=self._lock_token)
868
self._real_branch.repository.lock_write(
869
token=self._repo_lock_token)
871
self._real_branch.lock_write(token=self._lock_token)
873
self._real_branch.repository.unlock()
2430
874
if token is not None:
2431
875
self._leave_lock = True
877
# XXX: this case seems to be unreachable; token cannot be None.
2433
878
self._leave_lock = False
2434
879
self._lock_mode = 'w'
2435
880
self._lock_count = 1
2437
882
raise errors.ReadOnlyTransaction
2439
884
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
885
# A token was given to lock_write, and we're relocking, so check
886
# that the given token actually matches the one we already have.
2443
887
if token != self._lock_token:
2444
888
raise errors.TokenMismatch(token, self._lock_token)
2445
889
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
890
return self._lock_token
2450
892
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)
893
path = self.bzrdir._path_for_remote_call(self._client)
894
response = self._client.call('Branch.unlock', path, branch_token,
2455
896
if response == ('ok',):
2457
raise errors.UnexpectedSmartServerResponse(response)
898
elif response[0] == 'TokenMismatch':
899
raise errors.TokenMismatch(
900
str((branch_token, repo_token)), '(remote tokens)')
902
raise errors.UnexpectedSmartServerResponse(response)
2459
@only_raises(errors.LockNotHeld, errors.LockBroken)
2460
904
def unlock(self):
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
905
self._lock_count -= 1
906
if not self._lock_count:
907
self._clear_cached_state()
908
mode = self._lock_mode
909
self._lock_mode = None
910
if self._real_branch is not None:
2486
911
if not self._leave_lock:
2487
self._unlock(branch_token, repo_token)
2489
self.repository.unlock()
912
# If this RemoteBranch will remove the physical lock for the
913
# repository, make sure the _real_branch doesn't do it
914
# first. (Because the _real_branch's repository is set to
915
# be the RemoteRepository.)
916
self._real_branch.repository.leave_lock_in_place()
917
self._real_branch.unlock()
919
# Only write-locked branched need to make a remote method call
920
# to perfom the unlock.
922
assert self._lock_token, 'Locked, but no token!'
923
branch_token = self._lock_token
924
repo_token = self._repo_lock_token
925
self._lock_token = None
926
self._repo_lock_token = None
927
if not self._leave_lock:
928
self._unlock(branch_token, repo_token)
2491
930
def break_lock(self):
2492
931
self._ensure_real()
2493
932
return self._real_branch.break_lock()
2495
934
def leave_lock_in_place(self):
2496
if not self._lock_token:
2497
raise NotImplementedError(self.leave_lock_in_place)
2498
935
self._leave_lock = True
2500
937
def dont_leave_lock_in_place(self):
2501
if not self._lock_token:
2502
raise NotImplementedError(self.dont_leave_lock_in_place)
2503
938
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,))
940
def last_revision_info(self):
941
"""See Branch.last_revision_info()."""
942
path = self.bzrdir._path_for_remote_call(self._client)
943
response = self._client.call('Branch.last_revision_info', path)
944
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
2527
945
revno = int(response[1])
2528
946
last_revision = response[2]
2529
947
return (revno, last_revision)
2531
949
def _gen_revision_history(self):
2532
950
"""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')
951
path = self.bzrdir._path_for_remote_call(self._client)
952
response = self._client.call_expecting_body(
953
'Branch.revision_history', path)
954
assert response[0][0] == 'ok', ('unexpected response code %s'
956
result = response[1].read_body_bytes().split('\x00')
2541
957
if result == ['']:
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)
2592
961
@needs_write_lock
2593
962
def set_revision_history(self, rev_history):
2594
963
# Send just the tip revision of the history; the server will generate
2595
964
# the full history from that. If the revision doesn't exist in this
2596
965
# branch, NoSuchRevision will be raised.
966
path = self.bzrdir._path_for_remote_call(self._client)
2597
967
if rev_history == []:
2598
968
rev_id = 'null:'
2600
970
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)
971
self._clear_cached_state()
972
response = self._client.call('Branch.set_last_revision',
973
path, self._lock_token, self._repo_lock_token, rev_id)
974
if response[0] == 'NoSuchRevision':
975
raise NoSuchRevision(self, rev_id)
977
assert response == ('ok',), (
978
'unexpected response code %r' % (response,))
2604
979
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)
981
def get_parent(self):
983
return self._real_branch.get_parent()
985
def set_parent(self, url):
987
return self._real_branch.set_parent(url)
989
def get_config(self):
990
return RemoteBranchConfig(self)
992
def sprout(self, to_bzrdir, revision_id=None):
993
# Like Branch.sprout, except that it sprouts a branch in the default
994
# format, because RemoteBranches can't be created at arbitrary URLs.
995
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
996
# to_bzrdir.create_branch...
997
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
998
self.copy_content_into(result, revision_id=revision_id)
999
result.set_parent(self.bzrdir.root_transport.base)
1003
def append_revision(self, *revision_ids):
1005
return self._real_branch.append_revision(*revision_ids)
2647
1007
@needs_write_lock
2648
1008
def pull(self, source, overwrite=False, stop_revision=None,
2650
self._clear_cached_state_of_remote_branch_only()
1010
# FIXME: This asks the real branch to run the hooks, which means
1011
# they're called with the wrong target branch parameter.
1012
# The test suite specifically allows this at present but it should be
1013
# fixed. It should get a _override_hook_target branch,
1014
# as push does. -- mbp 20070405
2651
1015
self._ensure_real()
2652
return self._real_branch.pull(
1016
self._real_branch.pull(
2653
1017
source, overwrite=overwrite, stop_revision=stop_revision,
2654
_override_hook_target=self, **kwargs)
2656
1020
@needs_read_lock
2657
1021
def push(self, target, overwrite=False, stop_revision=None):
2663
1027
def is_locked(self):
2664
1028
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
1030
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)
1032
self._clear_cached_state()
1033
return self._real_branch.set_last_revision_info(revno, revision_id)
2699
1035
def generate_revision_history(self, revision_id, last_rev=None,
2700
1036
other_branch=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))
1038
return self._real_branch.generate_revision_history(
1039
revision_id, last_rev=last_rev, other_branch=other_branch)
1044
return self._real_branch.tags
2714
1046
def set_push_location(self, location):
2715
1047
self._ensure_real()
2716
1048
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
1050
def update_revisions(self, other, stop_revision=None):
1052
return self._real_branch.update_revisions(
1053
other, stop_revision=stop_revision)
1056
class RemoteBranchConfig(BranchConfig):
1059
self.branch._ensure_real()
1060
return self.branch._real_branch.get_config().username()
1062
def _get_branch_data_config(self):
1063
self.branch._ensure_real()
1064
if self._branch_data_config is None:
1065
self._branch_data_config = TreeConfig(self.branch._real_branch)
1066
return self._branch_data_config
2833
1069
def _extract_tar(tar, to_dir):
2838
1074
for tarinfo in tar:
2839
1075
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)