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
29
from bzrlib.branch import Branch, BranchReferenceFormat
37
30
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
39
from bzrlib.errors import (
31
from bzrlib.config import BranchConfig, TreeConfig
32
from bzrlib.decorators import needs_read_lock, needs_write_lock
33
from bzrlib.errors import NoSuchRevision
34
from bzrlib.lockable_files import LockableFiles
35
from bzrlib.pack import ContainerReader
36
from bzrlib.smart import client, vfs
37
from bzrlib.symbol_versioning import (
43
from bzrlib.lockable_files import LockableFiles
44
from bzrlib.smart import client, vfs, repository as smart_repo
45
from bzrlib.revision import ensure_null, NULL_REVISION
46
from bzrlib.trace import mutter, note, warning
49
class _RpcHelper(object):
50
"""Mixin class that helps with issuing RPCs."""
52
def _call(self, method, *args, **err_context):
54
return self._client.call(method, *args)
55
except errors.ErrorFromSmartServer, err:
56
self._translate_error(err, **err_context)
58
def _call_expecting_body(self, method, *args, **err_context):
60
return self._client.call_expecting_body(method, *args)
61
except errors.ErrorFromSmartServer, err:
62
self._translate_error(err, **err_context)
64
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
66
return self._client.call_with_body_bytes(method, args, body_bytes)
67
except errors.ErrorFromSmartServer, err:
68
self._translate_error(err, **err_context)
70
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
73
return self._client.call_with_body_bytes_expecting_body(
74
method, args, body_bytes)
75
except errors.ErrorFromSmartServer, err:
76
self._translate_error(err, **err_context)
79
def response_tuple_to_repo_format(response):
80
"""Convert a response tuple describing a repository format to a format."""
81
format = RemoteRepositoryFormat()
82
format._rich_root_data = (response[0] == 'yes')
83
format._supports_tree_reference = (response[1] == 'yes')
84
format._supports_external_lookups = (response[2] == 'yes')
85
format._network_name = response[3]
41
from bzrlib.trace import note
89
43
# Note: RemoteBzrDirFormat is in bzrdir.py
91
class RemoteBzrDir(BzrDir, _RpcHelper):
45
class RemoteBzrDir(BzrDir):
92
46
"""Control directory on a remote server, accessed via bzr:// or similar."""
94
def __init__(self, transport, format, _client=None, _force_probe=False):
48
def __init__(self, transport, _client=None):
95
49
"""Construct a RemoteBzrDir.
97
51
:param _client: Private parameter for testing. Disables probing and the
98
52
use of a real bzrdir.
100
BzrDir.__init__(self, transport, format)
54
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
101
55
# this object holds a delegated bzrdir that uses file-level operations
102
56
# to talk to the other side
103
57
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
59
if _client is None:
110
medium = transport.get_smart_medium()
111
self._client = client._SmartClient(medium)
60
self._shared_medium = transport.get_shared_medium()
61
self._client = client._SmartClient(self._shared_medium)
113
63
self._client = _client
120
return '%s(%r)' % (self.__class__.__name__, self._client)
122
def _probe_bzrdir(self):
123
medium = self._client._medium
64
self._shared_medium = None
124
67
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)
68
response = self._client.call('BzrDir.open', path)
151
69
if response not in [('yes',), ('no',)]:
152
70
raise errors.UnexpectedSmartServerResponse(response)
153
71
if response == ('no',):
154
raise errors.NotBranchError(path=self.root_transport.base)
72
raise errors.NotBranchError(path=transport.base)
156
74
def _ensure_real(self):
157
75
"""Ensure that there is a _real_bzrdir set.
159
77
Used before calls to self._real_bzrdir.
161
79
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
80
self._real_bzrdir = BzrDir.open_from_transport(
167
81
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
83
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()
85
self._real_bzrdir.create_repository(shared=shared)
86
return self.open_repository()
242
88
def destroy_repository(self):
243
89
"""See BzrDir.destroy_repository"""
244
90
self._ensure_real()
245
91
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
93
def create_branch(self):
95
real_branch = self._real_bzrdir.create_branch()
96
return RemoteBranch(self, self.find_repository(), real_branch)
266
def destroy_branch(self, name=None):
98
def destroy_branch(self):
267
99
"""See BzrDir.destroy_branch"""
268
100
self._ensure_real()
269
self._real_bzrdir.destroy_branch(name=name)
270
self._next_open_branch_result = None
101
self._real_bzrdir.destroy_branch()
272
103
def create_workingtree(self, revision_id=None, from_branch=None):
273
104
raise errors.NotLocalUrl(self.transport.base)
283
114
def get_branch_reference(self):
284
115
"""See BzrDir.get_branch_reference()."""
285
response = self._get_branch_reference()
286
if response[0] == 'ref':
291
def _get_branch_reference(self):
292
116
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'):
117
response = self._client.call('BzrDir.open_branch', path)
118
if response[0] == 'ok':
119
if response[1] == '':
120
# branch at this location.
123
# a branch reference, use the existing BranchReference logic.
125
elif response == ('nobranch',):
126
raise errors.NotBranchError(path=self.root_transport.base)
318
128
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':
130
def open_branch(self, _unsupported=False):
131
assert _unsupported == False, 'unsupported flag support not implemented yet.'
132
reference_url = self.get_branch_reference()
133
if reference_url is None:
134
# branch at this location.
135
return RemoteBranch(self, self.find_repository())
336
137
# a branch reference, use the existing BranchReference logic.
337
138
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
139
return format.open(self, _found=True, location=reference_url)
383
141
def open_repository(self):
384
142
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,))
143
response = self._client.call('BzrDir.find_repository', path)
144
assert response[0] in ('ok', 'norepository'), \
145
'unexpected response code %s' % (response,)
146
if response[0] == 'norepository':
147
raise errors.NoRepositoryPresent(self)
148
assert len(response) == 4, 'incorrect response length %s' % (response,)
399
149
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)
150
format = RemoteRepositoryFormat()
151
format.rich_root_data = (response[2] == 'yes')
152
format.supports_tree_reference = (response[3] == 'yes')
153
return RemoteRepository(self, format)
410
155
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
157
def open_workingtree(self, recommend_upgrade=True):
419
if self.has_workingtree():
159
if self._real_bzrdir.has_workingtree():
420
160
raise errors.NotLocalUrl(self.root_transport)
422
162
raise errors.NoWorkingTree(self.root_transport.base)
464
197
Instances of this repository are represented by RemoteRepository
467
The RemoteRepositoryFormat is parameterized during construction
200
The RemoteRepositoryFormat is parameterised during construction
468
201
to reflect the capabilities of the real, remote format. Specifically
469
202
the attributes rich_root_data and supports_tree_reference are set
470
203
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)
207
_matchingbzrdir = RemoteBzrDirFormat
557
209
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
210
assert isinstance(a_bzrdir, RemoteBzrDir), \
211
'%r is not a RemoteBzrDir' % (a_bzrdir,)
212
return a_bzrdir.create_repository(shared=shared)
597
214
def open(self, a_bzrdir):
598
if not isinstance(a_bzrdir, RemoteBzrDir):
599
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
215
assert isinstance(a_bzrdir, RemoteBzrDir)
600
216
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
218
def get_format_description(self):
624
return 'Remote: ' + self._custom_format.get_format_description()
219
return 'bzr remote repository'
626
221
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):
222
return self.__class__ == other.__class__
224
def check_conversion_target(self, target_format):
225
if self.rich_root_data and not target_format.rich_root_data:
226
raise errors.BadConversionTarget(
227
'Does not support rich root data.', target_format)
228
if (self.supports_tree_reference and
229
not getattr(target_format, 'supports_tree_reference', False)):
230
raise errors.BadConversionTarget(
231
'Does not support nested trees', target_format)
234
class RemoteRepository(object):
648
235
"""Repository accessed over rpc.
650
237
For the moment most operations are performed using local transport-backed
744
300
self._ensure_real()
745
301
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
303
def _ensure_real(self):
792
304
"""Ensure that there is a _real_repository set.
794
306
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()
308
if not self._real_repository:
810
309
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)
310
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
311
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
817
313
def find_text_key_references(self):
818
314
"""Find the text key references within the repository.
839
335
self._ensure_real()
840
336
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."""
338
def get_revision_graph(self, revision_id=None):
339
"""See Repository.get_revision_graph()."""
844
340
if revision_id is None:
846
342
elif revision.is_null(revision_id):
849
345
path = self.bzrdir._path_for_remote_call(self._client)
850
response = self._call_expecting_body(
346
assert type(revision_id) is str
347
response = self._client.call_expecting_body(
851
348
'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)
349
if response[0][0] not in ['ok', 'nosuchrevision']:
350
raise errors.UnexpectedSmartServerResponse(response[0])
351
if response[0][0] == 'ok':
352
coded = response[1].read_body_bytes()
354
# no revisions in this repository!
356
lines = coded.split('\n')
359
d = tuple(line.split())
360
revision_graph[d[0]] = d[1:]
362
return revision_graph
364
response_body = response[1].read_body_bytes()
365
assert response_body == ''
366
raise NoSuchRevision(self, revision_id)
876
368
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):
369
"""See Repository.has_revision()."""
370
if revision_id is None:
371
# The null revision is always present.
373
path = self.bzrdir._path_for_remote_call(self._client)
374
response = self._client.call('Repository.has_revision', path, revision_id)
375
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
376
return response[0] == 'yes'
908
378
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
379
return (self.__class__ == other.__class__ and
913
380
self.bzrdir.transport.base == other.bzrdir.transport.base)
915
382
def get_graph(self, other_repository=None):
916
383
"""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)
385
return self._real_repository.get_graph(other_repository)
929
387
def gather_stats(self, revid=None, committers=None):
930
388
"""See Repository.gather_stats()."""
1218
615
# FIXME: It ought to be possible to call this without immediately
1219
616
# triggering _ensure_real. For now it's the easiest thing to do.
1220
617
self._ensure_real()
1221
real_repo = self._real_repository
1222
builder = real_repo.get_commit_builder(branch, parents,
618
builder = self._real_repository.get_commit_builder(branch, parents,
1223
619
config, timestamp=timestamp, timezone=timezone,
1224
620
committer=committer, revprops=revprops, revision_id=revision_id)
1227
def add_fallback_repository(self, repository):
1228
"""Add a repository to use for looking up data not held locally.
1230
:param repository: A repository.
1232
if not self._format.supports_external_lookups:
1233
raise errors.UnstackableRepositoryFormat(
1234
self._format.network_name(), self.base)
1235
# We need to accumulate additional repositories here, to pass them in
1238
if self.is_locked():
1239
# We will call fallback.unlock() when we transition to the unlocked
1240
# state, so always add a lock here. If a caller passes us a locked
1241
# repository, they are responsible for unlocking it later.
1242
repository.lock_read()
1243
self._check_fallback_repository(repository)
1244
self._fallback_repositories.append(repository)
1245
# If self._real_repository was parameterised already (e.g. because a
1246
# _real_branch had its get_stacked_on_url method called), then the
1247
# repository to be added may already be in the _real_repositories list.
1248
if self._real_repository is not None:
1249
fallback_locations = [repo.user_url for repo in
1250
self._real_repository._fallback_repositories]
1251
if repository.user_url not in fallback_locations:
1252
self._real_repository.add_fallback_repository(repository)
1254
def _check_fallback_repository(self, repository):
1255
"""Check that this repository can fallback to repository safely.
1257
Raise an error if not.
1259
:param repository: A repository to fallback to.
1261
return _mod_repository.InterRepository._assert_same_model(
1264
624
def add_inventory(self, revid, inv, parents):
1265
625
self._ensure_real()
1266
626
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
629
def add_revision(self, rev_id, rev, inv=None, config=None):
1276
630
self._ensure_real()
1277
631
return self._real_repository.add_revision(
1301
656
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1303
658
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)):
659
"""RemoteRepositories never create working trees by default."""
662
def fetch(self, source, revision_id=None, pb=None):
663
if self.has_same_location(source):
1360
664
# check that last_revision is in 'from' and then return a
1362
666
if (revision_id is not None and
1363
667
not revision.is_null(revision_id)):
1364
668
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)
671
return self._real_repository.fetch(
672
source, revision_id=revision_id, pb=pb)
1373
674
def create_bundle(self, target, base, fileobj, format=None):
1374
675
self._ensure_real()
1375
676
self._real_repository.create_bundle(target, base, fileobj, format)
679
def control_weaves(self):
681
return self._real_repository.control_weaves
1377
683
@needs_read_lock
1378
684
def get_ancestry(self, revision_id, topo_sorted=True):
1379
685
self._ensure_real()
1380
686
return self._real_repository.get_ancestry(revision_id, topo_sorted)
689
def get_inventory_weave(self):
691
return self._real_repository.get_inventory_weave()
1382
693
def fileids_altered_by_revision_ids(self, revision_ids):
1383
694
self._ensure_real()
1384
695
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1387
698
self._ensure_real()
1388
699
return self._real_repository._get_versioned_file_checker(
1389
700
revisions, revision_versions_cache)
1391
702
def iter_files_bytes(self, desired_files):
1392
703
"""See Repository.iter_file_bytes.
1394
705
self._ensure_real()
1395
706
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
708
@needs_read_lock
1533
709
def get_signature_text(self, revision_id):
1534
710
self._ensure_real()
1535
711
return self._real_repository.get_signature_text(revision_id)
1537
713
@needs_read_lock
1538
def _get_inventory_xml(self, revision_id):
1540
return self._real_repository._get_inventory_xml(revision_id)
714
def get_revision_graph_with_ghosts(self, revision_ids=None):
716
return self._real_repository.get_revision_graph_with_ghosts(
717
revision_ids=revision_ids)
720
def get_inventory_xml(self, revision_id):
722
return self._real_repository.get_inventory_xml(revision_id)
724
def deserialise_inventory(self, revision_id, xml):
726
return self._real_repository.deserialise_inventory(revision_id, xml)
1542
728
def reconcile(self, other=None, thorough=False):
1543
729
self._ensure_real()
1544
730
return self._real_repository.reconcile(other=other, thorough=thorough)
1546
732
def all_revision_ids(self):
1547
733
self._ensure_real()
1548
734
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)
737
def get_deltas_for_revisions(self, revisions):
739
return self._real_repository.get_deltas_for_revisions(revisions)
742
def get_revision_delta(self, revision_id):
744
return self._real_repository.get_revision_delta(revision_id)
1562
746
@needs_read_lock
1563
747
def revision_trees(self, revision_ids):
1608
793
# TODO: Suggestion from john: using external tar is much faster than
1609
794
# python's tarfile library, but it may not work on windows.
1612
def inventories(self):
1613
"""Decorate the real repository for now.
1615
In the long term a full blown network facility is needed to
1616
avoid creating a real repository object locally.
1619
return self._real_repository.inventories
1621
796
@needs_write_lock
1622
def pack(self, hint=None, clean_obsolete_packs=False):
1623
798
"""Compress the data within the repository.
1625
800
This is not currently implemented within the smart server.
1627
802
self._ensure_real()
1628
return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1631
def revisions(self):
1632
"""Decorate the real repository for now.
1634
In the short term this should become a real object to intercept graph
1637
In the long term a full blown network facility is needed.
1640
return self._real_repository.revisions
803
return self._real_repository.pack()
1642
805
def set_make_working_trees(self, new_value):
1644
new_value_str = "True"
1646
new_value_str = "False"
1647
path = self.bzrdir._path_for_remote_call(self._client)
1649
response = self._call(
1650
'Repository.set_make_working_trees', path, new_value_str)
1651
except errors.UnknownSmartMethod:
1653
self._real_repository.set_make_working_trees(new_value)
1655
if response[0] != 'ok':
1656
raise errors.UnexpectedSmartServerResponse(response)
1659
def signatures(self):
1660
"""Decorate the real repository for now.
1662
In the long term a full blown network facility is needed to avoid
1663
creating a real repository object locally.
1666
return self._real_repository.signatures
806
raise NotImplementedError(self.set_make_working_trees)
1668
808
@needs_write_lock
1669
809
def sign_revision(self, revision_id, gpg_strategy):
1670
810
self._ensure_real()
1671
811
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1675
"""Decorate the real repository for now.
1677
In the long term a full blown network facility is needed to avoid
1678
creating a real repository object locally.
1681
return self._real_repository.texts
1683
813
@needs_read_lock
1684
814
def get_revisions(self, revision_ids):
1685
815
self._ensure_real()
1686
816
return self._real_repository.get_revisions(revision_ids)
1688
818
def supports_rich_root(self):
1689
return self._format.rich_root_data
820
return self._real_repository.supports_rich_root()
1691
822
def iter_reverse_revision_history(self, revision_id):
1692
823
self._ensure_real()
1719
885
self._ensure_real()
1720
886
return self._real_repository.revision_graph_can_have_wrong_parents()
1722
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
888
def _find_inconsistent_revision_parents(self):
1723
889
self._ensure_real()
1724
return self._real_repository._find_inconsistent_revision_parents(
890
return self._real_repository._find_inconsistent_revision_parents()
1727
892
def _check_for_inconsistent_revision_parents(self):
1728
893
self._ensure_real()
1729
894
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
897
class RemoteBranchLockableFiles(LockableFiles):
2018
898
"""A 'LockableFiles' implementation that talks to a smart server.
2020
900
This is not a public interface class.
2033
913
self._dir_mode = None
2034
914
self._file_mode = None
917
"""'get' a remote path as per the LockableFiles interface.
919
:param path: the file to 'get'. If this is 'branch.conf', we do not
920
just retrieve a file, instead we ask the smart server to generate
921
a configuration for us - which is retrieved as an INI file.
923
if path == 'branch.conf':
924
path = self.bzrdir._path_for_remote_call(self._client)
925
response = self._client.call_expecting_body(
926
'Branch.get_config_file', path)
927
assert response[0][0] == 'ok', \
928
'unexpected response code %s' % (response[0],)
929
return StringIO(response[1].read_body_bytes())
932
return LockableFiles.get(self, path)
2037
935
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
937
def __eq__(self, other):
2047
return (isinstance(other, RemoteBranchFormat) and
938
return (isinstance(other, RemoteBranchFormat) and
2048
939
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
941
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)
942
return 'Remote BZR Branch'
944
def get_format_string(self):
945
return 'Remote BZR Branch'
947
def open(self, a_bzrdir):
948
assert isinstance(a_bzrdir, RemoteBzrDir)
949
return a_bzrdir.open_branch()
951
def initialize(self, a_bzrdir):
952
assert isinstance(a_bzrdir, RemoteBzrDir)
953
return a_bzrdir.create_branch()
2137
955
def supports_tags(self):
2138
956
# Remote branches might support tags, but we won't know until we
2139
957
# 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):
961
class RemoteBranch(branch.Branch):
2153
962
"""Branch stored on a server accessed by HPSS RPC.
2155
964
At the moment most operations are mapped down to simple file operations.
2158
967
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2159
_client=None, format=None, setup_stacking=True, name=None):
2160
969
"""Create a RemoteBranch instance.
2162
971
:param real_branch: An optional local implementation of the branch
2163
972
format, usually accessing the data via the VFS.
2164
973
: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
975
# We intentionally don't call the parent class's __init__, because it
2174
976
# will try to assign to self.tags, which is a property in this subclass.
2175
977
# And the parent's __init__ doesn't do much anyway.
978
self._revision_id_to_revno_cache = None
979
self._revision_history_cache = None
2176
980
self.bzrdir = remote_bzrdir
2177
981
if _client is not None:
2178
982
self._client = _client
2180
self._client = remote_bzrdir._client
984
self._client = client._SmartClient(self.bzrdir._shared_medium)
2181
985
self.repository = remote_repository
2182
986
if real_branch is not None:
2183
987
self._real_branch = real_branch
2261
1015
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')
1017
if not self._real_branch:
1018
assert vfs.vfs_enabled()
2267
1019
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
1020
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1021
# Give the remote repository the matching real repo.
1022
real_repo = self._real_branch.repository
1023
if isinstance(real_repo, RemoteRepository):
1024
real_repo._ensure_real()
1025
real_repo = real_repo._real_repository
1026
self.repository._set_real_repository(real_repo)
1027
# Give the branch the remote repository to let fast-pathing happen.
2279
1028
self._real_branch.repository = self.repository
1029
# XXX: deal with _lock_mode == 'w'
2280
1030
if self._lock_mode == 'r':
2281
1031
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
1034
def control_files(self):
2321
1049
self._ensure_real()
2322
1050
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
1052
def lock_read(self):
2390
self.repository.lock_read()
2391
1053
if not self._lock_mode:
2392
self._note_lock('r')
2393
1054
self._lock_mode = 'r'
2394
1055
self._lock_count = 1
2395
1056
if self._real_branch is not None:
2404
1065
branch_token = token
2405
1066
repo_token = self.repository.lock_write()
2406
1067
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':
1068
path = self.bzrdir._path_for_remote_call(self._client)
1069
response = self._client.call('Branch.lock_write', path, branch_token,
1071
if response[0] == 'ok':
1072
ok, branch_token, repo_token = response
1073
return branch_token, repo_token
1074
elif response[0] == 'LockContention':
1075
raise errors.LockContention('(remote lock)')
1076
elif response[0] == 'TokenMismatch':
1077
raise errors.TokenMismatch(token, '(remote token)')
1078
elif response[0] == 'UnlockableTransport':
1079
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1080
elif response[0] == 'ReadOnlyError':
1081
raise errors.ReadOnlyError(self)
1082
elif response[0] == 'LockFailed':
1083
raise errors.LockFailed(response[1], response[2])
2412
1085
raise errors.UnexpectedSmartServerResponse(response)
2413
ok, branch_token, repo_token = response
2414
return branch_token, repo_token
2416
1087
def lock_write(self, token=None):
2417
1088
if not self._lock_mode:
2418
self._note_lock('w')
2419
# Lock the branch and repo in one remote call.
2420
1089
remote_tokens = self._remote_lock_write(token)
2421
1090
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)
1091
assert self._lock_token, 'Remote server did not return a token!'
1092
# TODO: We really, really, really don't want to call _ensure_real
1093
# here, but it's the easiest way to ensure coherency between the
1094
# state of the RemoteBranch and RemoteRepository objects and the
1095
# physical locks. If we don't materialise the real objects here,
1096
# then getting everything in the right state later is complex, so
1097
# for now we just do it the lazy way.
1098
# -- Andrew Bennetts, 2007-02-22.
2428
1100
if self._real_branch is not None:
2429
self._real_branch.lock_write(token=self._lock_token)
1101
self._real_branch.repository.lock_write(
1102
token=self._repo_lock_token)
1104
self._real_branch.lock_write(token=self._lock_token)
1106
self._real_branch.repository.unlock()
2430
1107
if token is not None:
2431
1108
self._leave_lock = True
1110
# XXX: this case seems to be unreachable; token cannot be None.
2433
1111
self._leave_lock = False
2434
1112
self._lock_mode = 'w'
2435
1113
self._lock_count = 1
2437
1115
raise errors.ReadOnlyTransaction
2439
1117
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
1118
# A token was given to lock_write, and we're relocking, so check
1119
# that the given token actually matches the one we already have.
2443
1120
if token != self._lock_token:
2444
1121
raise errors.TokenMismatch(token, self._lock_token)
2445
1122
self._lock_count += 1
2446
# Re-lock the repository too.
2447
self.repository.lock_write(self._repo_lock_token)
2448
1123
return self._lock_token or None
2450
1125
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)
1126
path = self.bzrdir._path_for_remote_call(self._client)
1127
response = self._client.call('Branch.unlock', path, branch_token,
2455
1129
if response == ('ok',):
2457
raise errors.UnexpectedSmartServerResponse(response)
1131
elif response[0] == 'TokenMismatch':
1132
raise errors.TokenMismatch(
1133
str((branch_token, repo_token)), '(remote tokens)')
1135
raise errors.UnexpectedSmartServerResponse(response)
2459
@only_raises(errors.LockNotHeld, errors.LockBroken)
2460
1137
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
2486
if not self._leave_lock:
2487
self._unlock(branch_token, repo_token)
2489
self.repository.unlock()
1138
self._lock_count -= 1
1139
if not self._lock_count:
1140
self._clear_cached_state()
1141
mode = self._lock_mode
1142
self._lock_mode = None
1143
if self._real_branch is not None:
1144
if (not self._leave_lock and mode == 'w' and
1145
self._repo_lock_token):
1146
# If this RemoteBranch will remove the physical lock for the
1147
# repository, make sure the _real_branch doesn't do it
1148
# first. (Because the _real_branch's repository is set to
1149
# be the RemoteRepository.)
1150
self._real_branch.repository.leave_lock_in_place()
1151
self._real_branch.unlock()
1153
# Only write-locked branched need to make a remote method call
1154
# to perfom the unlock.
1156
assert self._lock_token, 'Locked, but no token!'
1157
branch_token = self._lock_token
1158
repo_token = self._repo_lock_token
1159
self._lock_token = None
1160
self._repo_lock_token = None
1161
if not self._leave_lock:
1162
self._unlock(branch_token, repo_token)
2491
1164
def break_lock(self):
2492
1165
self._ensure_real()
2502
1175
raise NotImplementedError(self.dont_leave_lock_in_place)
2503
1176
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,))
1178
def last_revision_info(self):
1179
"""See Branch.last_revision_info()."""
1180
path = self.bzrdir._path_for_remote_call(self._client)
1181
response = self._client.call('Branch.last_revision_info', path)
1182
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
2527
1183
revno = int(response[1])
2528
1184
last_revision = response[2]
2529
1185
return (revno, last_revision)
2531
1187
def _gen_revision_history(self):
2532
1188
"""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')
1189
path = self.bzrdir._path_for_remote_call(self._client)
1190
response = self._client.call_expecting_body(
1191
'Branch.revision_history', path)
1192
assert response[0][0] == 'ok', ('unexpected response code %s'
1194
result = response[1].read_body_bytes().split('\x00')
2541
1195
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
1199
@needs_write_lock
2593
1200
def set_revision_history(self, rev_history):
2594
1201
# Send just the tip revision of the history; the server will generate
2595
1202
# the full history from that. If the revision doesn't exist in this
2596
1203
# branch, NoSuchRevision will be raised.
1204
path = self.bzrdir._path_for_remote_call(self._client)
2597
1205
if rev_history == []:
2598
1206
rev_id = 'null:'
2600
1208
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)
1209
self._clear_cached_state()
1210
response = self._client.call('Branch.set_last_revision',
1211
path, self._lock_token, self._repo_lock_token, rev_id)
1212
if response[0] == 'NoSuchRevision':
1213
raise NoSuchRevision(self, rev_id)
1215
assert response == ('ok',), (
1216
'unexpected response code %r' % (response,))
2604
1217
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)
1219
def get_parent(self):
1221
return self._real_branch.get_parent()
1223
def set_parent(self, url):
1225
return self._real_branch.set_parent(url)
1227
def get_config(self):
1228
return RemoteBranchConfig(self)
1230
def sprout(self, to_bzrdir, revision_id=None):
1231
# Like Branch.sprout, except that it sprouts a branch in the default
1232
# format, because RemoteBranches can't be created at arbitrary URLs.
1233
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1234
# to_bzrdir.create_branch...
1235
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1236
self.copy_content_into(result, revision_id=revision_id)
1237
result.set_parent(self.bzrdir.root_transport.base)
2647
1240
@needs_write_lock
2648
1241
def pull(self, source, overwrite=False, stop_revision=None,
2650
self._clear_cached_state_of_remote_branch_only()
1243
# FIXME: This asks the real branch to run the hooks, which means
1244
# they're called with the wrong target branch parameter.
1245
# The test suite specifically allows this at present but it should be
1246
# fixed. It should get a _override_hook_target branch,
1247
# as push does. -- mbp 20070405
2651
1248
self._ensure_real()
2652
return self._real_branch.pull(
1249
self._real_branch.pull(
2653
1250
source, overwrite=overwrite, stop_revision=stop_revision,
2654
_override_hook_target=self, **kwargs)
2656
1253
@needs_read_lock
2657
1254
def push(self, target, overwrite=False, stop_revision=None):
2663
1260
def is_locked(self):
2664
1261
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
1263
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)
1265
self._clear_cached_state()
1266
return self._real_branch.set_last_revision_info(revno, revision_id)
2699
1268
def generate_revision_history(self, revision_id, last_rev=None,
2700
1269
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))
1271
return self._real_branch.generate_revision_history(
1272
revision_id, last_rev=last_rev, other_branch=other_branch)
1277
return self._real_branch.tags
2714
1279
def set_push_location(self, location):
2715
1280
self._ensure_real()
2716
1281
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
1283
def update_revisions(self, other, stop_revision=None):
1285
return self._real_branch.update_revisions(
1286
other, stop_revision=stop_revision)
1289
class RemoteBranchConfig(BranchConfig):
1292
self.branch._ensure_real()
1293
return self.branch._real_branch.get_config().username()
1295
def _get_branch_data_config(self):
1296
self.branch._ensure_real()
1297
if self._branch_data_config is None:
1298
self._branch_data_config = TreeConfig(self.branch._real_branch)
1299
return self._branch_data_config
2833
1302
def _extract_tar(tar, to_dir):
2838
1307
for tarinfo in tar:
2839
1308
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)