13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
20
from cStringIO import StringIO
19
22
from bzrlib import (
30
repository as _mod_repository,
32
revision as _mod_revision,
36
from bzrlib.branch import BranchReferenceFormat
28
from bzrlib.branch import Branch, BranchReferenceFormat
37
29
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
39
from bzrlib.errors import (
30
from bzrlib.config import BranchConfig, TreeConfig
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
32
from bzrlib.errors import NoSuchRevision
33
from bzrlib.lockable_files import LockableFiles
34
from bzrlib.pack import ContainerReader
35
from bzrlib.revision import NULL_REVISION
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()
242
def destroy_repository(self):
243
"""See BzrDir.destroy_repository"""
245
self._real_bzrdir.destroy_repository()
247
def create_branch(self, name=None):
248
# as per meta1 formats - just delegate to the format object which may
250
real_branch = self._format.get_branch_format().initialize(self,
252
if not isinstance(real_branch, RemoteBranch):
253
result = RemoteBranch(self, self.find_repository(), real_branch,
257
# BzrDir.clone_on_transport() uses the result of create_branch but does
258
# not return it to its callers; we save approximately 8% of our round
259
# trips by handing the branch we created back to the first caller to
260
# open_branch rather than probing anew. Long term we need a API in
261
# bzrdir that doesn't discard result objects (like result_branch).
263
self._next_open_branch_result = result
266
def destroy_branch(self, name=None):
85
self._real_bzrdir.create_repository(shared=shared)
86
return self.open_repository()
88
def create_branch(self):
90
real_branch = self._real_bzrdir.create_branch()
91
return RemoteBranch(self, self.find_repository(), real_branch)
93
def destroy_branch(self):
267
94
"""See BzrDir.destroy_branch"""
268
95
self._ensure_real()
269
self._real_bzrdir.destroy_branch(name=name)
270
self._next_open_branch_result = None
96
self._real_bzrdir.destroy_branch()
272
def create_workingtree(self, revision_id=None, from_branch=None):
98
def create_workingtree(self, revision_id=None):
273
99
raise errors.NotLocalUrl(self.transport.base)
275
101
def find_branch_format(self):
283
109
def get_branch_reference(self):
284
110
"""See BzrDir.get_branch_reference()."""
285
response = self._get_branch_reference()
286
if response[0] == 'ref':
291
def _get_branch_reference(self):
292
111
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'):
112
response = self._client.call('BzrDir.open_branch', path)
113
if response[0] == 'ok':
114
if response[1] == '':
115
# branch at this location.
118
# a branch reference, use the existing BranchReference logic.
120
elif response == ('nobranch',):
121
raise errors.NotBranchError(path=self.root_transport.base)
318
123
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':
125
def open_branch(self, _unsupported=False):
126
assert _unsupported == False, 'unsupported flag support not implemented yet.'
127
reference_url = self.get_branch_reference()
128
if reference_url is None:
129
# branch at this location.
130
return RemoteBranch(self, self.find_repository())
336
132
# a branch reference, use the existing BranchReference logic.
337
133
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
134
return format.open(self, _found=True, location=reference_url)
383
136
def open_repository(self):
384
137
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,))
138
response = self._client.call('BzrDir.find_repository', path)
139
assert response[0] in ('ok', 'norepository'), \
140
'unexpected response code %s' % (response,)
141
if response[0] == 'norepository':
142
raise errors.NoRepositoryPresent(self)
143
assert len(response) == 4, 'incorrect response length %s' % (response,)
399
144
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)
145
format = RemoteRepositoryFormat()
146
format.rich_root_data = (response[2] == 'yes')
147
format.supports_tree_reference = (response[3] == 'yes')
148
return RemoteRepository(self, format)
410
150
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
152
def open_workingtree(self, recommend_upgrade=True):
419
if self.has_workingtree():
154
if self._real_bzrdir.has_workingtree():
420
155
raise errors.NotLocalUrl(self.root_transport)
422
157
raise errors.NoWorkingTree(self.root_transport.base)
464
192
Instances of this repository are represented by RemoteRepository
467
The RemoteRepositoryFormat is parameterized during construction
195
The RemoteRepositoryFormat is parameterised during construction
468
196
to reflect the capabilities of the real, remote format. Specifically
469
197
the attributes rich_root_data and supports_tree_reference are set
470
198
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)
202
_matchingbzrdir = RemoteBzrDirFormat
557
204
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
205
assert isinstance(a_bzrdir, RemoteBzrDir), \
206
'%r is not a RemoteBzrDir' % (a_bzrdir,)
207
return a_bzrdir.create_repository(shared=shared)
597
209
def open(self, a_bzrdir):
598
if not isinstance(a_bzrdir, RemoteBzrDir):
599
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
210
assert isinstance(a_bzrdir, RemoteBzrDir)
600
211
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
213
def get_format_description(self):
624
return 'Remote: ' + self._custom_format.get_format_description()
214
return 'bzr remote repository'
626
216
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):
217
return self.__class__ == other.__class__
219
def check_conversion_target(self, target_format):
220
if self.rich_root_data and not target_format.rich_root_data:
221
raise errors.BadConversionTarget(
222
'Does not support rich root data.', target_format)
223
if (self.supports_tree_reference and
224
not getattr(target_format, 'supports_tree_reference', False)):
225
raise errors.BadConversionTarget(
226
'Does not support nested trees', target_format)
229
class RemoteRepository(object):
648
230
"""Repository accessed over rpc.
650
232
For the moment most operations are performed using local transport-backed
744
289
self._ensure_real()
745
290
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
292
def _ensure_real(self):
792
293
"""Ensure that there is a _real_repository set.
794
295
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()
297
if not self._real_repository:
810
298
self.bzrdir._ensure_real()
811
self._set_real_repository(
812
self.bzrdir._real_bzrdir.open_repository())
814
def _translate_error(self, err, **context):
815
self.bzrdir._translate_error(err, repository=self, **context)
817
def find_text_key_references(self):
818
"""Find the text key references within the repository.
820
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
821
revision_ids. Each altered file-ids has the exact revision_ids that
822
altered it listed explicitly.
823
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
824
to whether they were referred to by the inventory of the
825
revision_id that they contain. The inventory texts from all present
826
revision ids are assessed to generate this report.
829
return self._real_repository.find_text_key_references()
831
def _generate_text_key_index(self):
832
"""Generate a new text key index for the repository.
834
This is an expensive function that will take considerable time to run.
836
:return: A dict mapping (file_id, revision_id) tuples to a list of
837
parents, also (file_id, revision_id) tuples.
840
return self._real_repository._generate_text_key_index()
842
def _get_revision_graph(self, revision_id):
843
"""Private method for using with old (< 1.2) servers to fallback."""
299
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
300
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
302
def get_revision_graph(self, revision_id=None):
303
"""See Repository.get_revision_graph()."""
844
304
if revision_id is None:
846
elif revision.is_null(revision_id):
306
elif revision_id == NULL_REVISION:
849
309
path = self.bzrdir._path_for_remote_call(self._client)
850
response = self._call_expecting_body(
310
assert type(revision_id) is str
311
response = self._client.call_expecting_body(
851
312
'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)
313
if response[0][0] not in ['ok', 'nosuchrevision']:
314
raise errors.UnexpectedSmartServerResponse(response[0])
315
if response[0][0] == 'ok':
316
coded = response[1].read_body_bytes()
318
# no revisions in this repository!
320
lines = coded.split('\n')
323
d = tuple(line.split())
324
revision_graph[d[0]] = d[1:]
326
return revision_graph
328
response_body = response[1].read_body_bytes()
329
assert response_body == ''
330
raise NoSuchRevision(self, revision_id)
876
332
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):
333
"""See Repository.has_revision()."""
334
if revision_id is None:
335
# The null revision is always present.
337
path = self.bzrdir._path_for_remote_call(self._client)
338
response = self._client.call('Repository.has_revision', path, revision_id)
339
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
340
return response[0] == 'yes'
908
342
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
343
return (self.__class__ == other.__class__ and
913
344
self.bzrdir.transport.base == other.bzrdir.transport.base)
915
346
def get_graph(self, other_repository=None):
916
347
"""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)
348
return self._real_repository.get_graph(other_repository)
929
350
def gather_stats(self, revid=None, committers=None):
930
351
"""See Repository.gather_stats()."""
931
352
path = self.bzrdir._path_for_remote_call(self._client)
932
# revid can be None to indicate no revisions, not just NULL_REVISION
933
if revid is None or revision.is_null(revid):
353
if revid in (None, NULL_REVISION):
936
356
fmt_revid = revid
1218
559
# FIXME: It ought to be possible to call this without immediately
1219
560
# triggering _ensure_real. For now it's the easiest thing to do.
1220
561
self._ensure_real()
1221
real_repo = self._real_repository
1222
builder = real_repo.get_commit_builder(branch, parents,
562
builder = self._real_repository.get_commit_builder(branch, parents,
1223
563
config, timestamp=timestamp, timezone=timezone,
1224
564
committer=committer, revprops=revprops, revision_id=revision_id)
565
# Make the builder use this RemoteRepository rather than the real one.
566
builder.repository = self
1227
def add_fallback_repository(self, repository):
1228
"""Add a repository to use for looking up data not held locally.
1230
:param repository: A repository.
1232
if not self._format.supports_external_lookups:
1233
raise errors.UnstackableRepositoryFormat(
1234
self._format.network_name(), self.base)
1235
# We need to accumulate additional repositories here, to pass them in
1238
if self.is_locked():
1239
# We will call fallback.unlock() when we transition to the unlocked
1240
# state, so always add a lock here. If a caller passes us a locked
1241
# repository, they are responsible for unlocking it later.
1242
repository.lock_read()
1243
self._check_fallback_repository(repository)
1244
self._fallback_repositories.append(repository)
1245
# If self._real_repository was parameterised already (e.g. because a
1246
# _real_branch had its get_stacked_on_url method called), then the
1247
# repository to be added may already be in the _real_repositories list.
1248
if self._real_repository is not None:
1249
fallback_locations = [repo.user_url for repo in
1250
self._real_repository._fallback_repositories]
1251
if repository.user_url not in fallback_locations:
1252
self._real_repository.add_fallback_repository(repository)
1254
def _check_fallback_repository(self, repository):
1255
"""Check that this repository can fallback to repository safely.
1257
Raise an error if not.
1259
:param repository: A repository to fallback to.
1261
return _mod_repository.InterRepository._assert_same_model(
1264
570
def add_inventory(self, revid, inv, parents):
1265
571
self._ensure_real()
1266
572
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
575
def add_revision(self, rev_id, rev, inv=None, config=None):
1276
576
self._ensure_real()
1277
577
return self._real_repository.add_revision(
1301
602
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1303
604
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)):
605
"""RemoteRepositories never create working trees by default."""
608
def fetch(self, source, revision_id=None, pb=None):
609
if self.has_same_location(source):
1360
610
# check that last_revision is in 'from' and then return a
1362
612
if (revision_id is not None and
1363
not revision.is_null(revision_id)):
613
not _mod_revision.is_null(revision_id)):
1364
614
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)
617
return self._real_repository.fetch(
618
source, revision_id=revision_id, pb=pb)
1373
620
def create_bundle(self, target, base, fileobj, format=None):
1374
621
self._ensure_real()
1375
622
self._real_repository.create_bundle(target, base, fileobj, format)
625
def control_weaves(self):
627
return self._real_repository.control_weaves
1377
629
@needs_read_lock
1378
630
def get_ancestry(self, revision_id, topo_sorted=True):
1379
631
self._ensure_real()
1380
632
return self._real_repository.get_ancestry(revision_id, topo_sorted)
635
def get_inventory_weave(self):
637
return self._real_repository.get_inventory_weave()
1382
639
def fileids_altered_by_revision_ids(self, revision_ids):
1383
640
self._ensure_real()
1384
641
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1386
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1388
return self._real_repository._get_versioned_file_checker(
1389
revisions, revision_versions_cache)
1391
643
def iter_files_bytes(self, desired_files):
1392
644
"""See Repository.iter_file_bytes.
1394
646
self._ensure_real()
1395
647
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
649
@needs_read_lock
1533
650
def get_signature_text(self, revision_id):
1534
651
self._ensure_real()
1535
652
return self._real_repository.get_signature_text(revision_id)
1537
654
@needs_read_lock
1538
def _get_inventory_xml(self, revision_id):
1540
return self._real_repository._get_inventory_xml(revision_id)
655
def get_revision_graph_with_ghosts(self, revision_ids=None):
657
return self._real_repository.get_revision_graph_with_ghosts(
658
revision_ids=revision_ids)
661
def get_inventory_xml(self, revision_id):
663
return self._real_repository.get_inventory_xml(revision_id)
665
def deserialise_inventory(self, revision_id, xml):
667
return self._real_repository.deserialise_inventory(revision_id, xml)
1542
669
def reconcile(self, other=None, thorough=False):
1543
670
self._ensure_real()
1544
671
return self._real_repository.reconcile(other=other, thorough=thorough)
1546
673
def all_revision_ids(self):
1547
674
self._ensure_real()
1548
675
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)
678
def get_deltas_for_revisions(self, revisions):
680
return self._real_repository.get_deltas_for_revisions(revisions)
683
def get_revision_delta(self, revision_id):
685
return self._real_repository.get_revision_delta(revision_id)
1562
687
@needs_read_lock
1563
688
def revision_trees(self, revision_ids):
1696
768
def _serializer(self):
1697
return self._format._serializer
770
return self._real_repository._serializer
1699
772
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1700
773
self._ensure_real()
1701
774
return self._real_repository.store_revision_signature(
1702
775
gpg_strategy, plaintext, revision_id)
1704
def add_signature_text(self, revision_id, signature):
1706
return self._real_repository.add_signature_text(revision_id, signature)
1708
777
def has_signature_for_revision_id(self, revision_id):
1709
778
self._ensure_real()
1710
779
return self._real_repository.has_signature_for_revision_id(revision_id)
781
def get_data_stream(self, revision_ids):
782
path = self.bzrdir._path_for_remote_call(self._client)
783
response, protocol = self._client.call_expecting_body(
784
'Repository.stream_knit_data_for_revisions', path, *revision_ids)
785
if response == ('ok',):
786
return self._deserialise_stream(protocol)
787
elif (response == ('error', "Generic bzr smart protocol error: "
788
"bad request 'Repository.stream_knit_data_for_revisions'") or
789
response == ('error', "Generic bzr smart protocol error: "
790
"bad request u'Repository.stream_knit_data_for_revisions'")):
791
protocol.cancel_read_body()
793
return self._real_repository.get_data_stream(revision_ids)
795
raise errors.UnexpectedSmartServerResponse(response)
797
def _deserialise_stream(self, protocol):
798
buffer = StringIO(protocol.read_body_bytes())
799
reader = ContainerReader(buffer)
800
for record_names, read_bytes in reader.iter_records():
802
# These records should have only one name, and that name
803
# should be a one-element tuple.
804
[name_tuple] = record_names
806
raise errors.SmartProtocolError(
807
'Repository data stream had invalid record name %r'
809
yield name_tuple, read_bytes(None)
811
def insert_data_stream(self, stream):
813
self._real_repository.insert_data_stream(stream)
1712
815
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1713
816
self._ensure_real()
1714
817
return self._real_repository.item_keys_introduced_by(revision_ids,
1715
818
_files_pb=_files_pb)
1717
def revision_graph_can_have_wrong_parents(self):
1718
# The answer depends on the remote repo format.
1720
return self._real_repository.revision_graph_can_have_wrong_parents()
1722
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1724
return self._real_repository._find_inconsistent_revision_parents(
1727
def _check_for_inconsistent_revision_parents(self):
1729
return self._real_repository._check_for_inconsistent_revision_parents()
1731
def _make_parents_provider(self, other=None):
1732
providers = [self._unstacked_provider]
1733
if other is not None:
1734
providers.insert(0, other)
1735
providers.extend(r._make_parents_provider() for r in
1736
self._fallback_repositories)
1737
return graph.StackedParentsProvider(providers)
1739
def _serialise_search_recipe(self, recipe):
1740
"""Serialise a graph search recipe.
1742
:param recipe: A search recipe (start, stop, count).
1743
:return: Serialised bytes.
1745
start_keys = ' '.join(recipe[1])
1746
stop_keys = ' '.join(recipe[2])
1747
count = str(recipe[3])
1748
return '\n'.join((start_keys, stop_keys, count))
1750
def _serialise_search_result(self, search_result):
1751
if isinstance(search_result, graph.PendingAncestryResult):
1752
parts = ['ancestry-of']
1753
parts.extend(search_result.heads)
1755
recipe = search_result.get_recipe()
1756
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1757
return '\n'.join(parts)
1760
path = self.bzrdir._path_for_remote_call(self._client)
1762
response = self._call('PackRepository.autopack', path)
1763
except errors.UnknownSmartMethod:
1765
self._real_repository._pack_collection.autopack()
1768
if response[0] != 'ok':
1769
raise errors.UnexpectedSmartServerResponse(response)
1772
class RemoteStreamSink(repository.StreamSink):
1774
def _insert_real(self, stream, src_format, resume_tokens):
1775
self.target_repo._ensure_real()
1776
sink = self.target_repo._real_repository._get_sink()
1777
result = sink.insert_stream(stream, src_format, resume_tokens)
1779
self.target_repo.autopack()
1782
def insert_stream(self, stream, src_format, resume_tokens):
1783
target = self.target_repo
1784
target._unstacked_provider.missing_keys.clear()
1785
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1786
if target._lock_token:
1787
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1788
lock_args = (target._lock_token or '',)
1790
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1792
client = target._client
1793
medium = client._medium
1794
path = target.bzrdir._path_for_remote_call(client)
1795
# Probe for the verb to use with an empty stream before sending the
1796
# real stream to it. We do this both to avoid the risk of sending a
1797
# large request that is then rejected, and because we don't want to
1798
# implement a way to buffer, rewind, or restart the stream.
1800
for verb, required_version in candidate_calls:
1801
if medium._is_remote_before(required_version):
1804
# We've already done the probing (and set _is_remote_before) on
1805
# a previous insert.
1808
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1810
response = client.call_with_body_stream(
1811
(verb, path, '') + lock_args, byte_stream)
1812
except errors.UnknownSmartMethod:
1813
medium._remember_remote_is_before(required_version)
1819
return self._insert_real(stream, src_format, resume_tokens)
1820
self._last_inv_record = None
1821
self._last_substream = None
1822
if required_version < (1, 19):
1823
# Remote side doesn't support inventory deltas. Wrap the stream to
1824
# make sure we don't send any. If the stream contains inventory
1825
# deltas we'll interrupt the smart insert_stream request and
1827
stream = self._stop_stream_if_inventory_delta(stream)
1828
byte_stream = smart_repo._stream_to_byte_stream(
1830
resume_tokens = ' '.join(resume_tokens)
1831
response = client.call_with_body_stream(
1832
(verb, path, resume_tokens) + lock_args, byte_stream)
1833
if response[0][0] not in ('ok', 'missing-basis'):
1834
raise errors.UnexpectedSmartServerResponse(response)
1835
if self._last_substream is not None:
1836
# The stream included an inventory-delta record, but the remote
1837
# side isn't new enough to support them. So we need to send the
1838
# rest of the stream via VFS.
1839
self.target_repo.refresh_data()
1840
return self._resume_stream_with_vfs(response, src_format)
1841
if response[0][0] == 'missing-basis':
1842
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1843
resume_tokens = tokens
1844
return resume_tokens, set(missing_keys)
1846
self.target_repo.refresh_data()
1849
def _resume_stream_with_vfs(self, response, src_format):
1850
"""Resume sending a stream via VFS, first resending the record and
1851
substream that couldn't be sent via an insert_stream verb.
1853
if response[0][0] == 'missing-basis':
1854
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1855
# Ignore missing_keys, we haven't finished inserting yet
1858
def resume_substream():
1859
# Yield the substream that was interrupted.
1860
for record in self._last_substream:
1862
self._last_substream = None
1863
def resume_stream():
1864
# Finish sending the interrupted substream
1865
yield ('inventory-deltas', resume_substream())
1866
# Then simply continue sending the rest of the stream.
1867
for substream_kind, substream in self._last_stream:
1868
yield substream_kind, substream
1869
return self._insert_real(resume_stream(), src_format, tokens)
1871
def _stop_stream_if_inventory_delta(self, stream):
1872
"""Normally this just lets the original stream pass-through unchanged.
1874
However if any 'inventory-deltas' substream occurs it will stop
1875
streaming, and store the interrupted substream and stream in
1876
self._last_substream and self._last_stream so that the stream can be
1877
resumed by _resume_stream_with_vfs.
1880
stream_iter = iter(stream)
1881
for substream_kind, substream in stream_iter:
1882
if substream_kind == 'inventory-deltas':
1883
self._last_substream = substream
1884
self._last_stream = stream_iter
1887
yield substream_kind, substream
1890
class RemoteStreamSource(repository.StreamSource):
1891
"""Stream data from a remote server."""
1893
def get_stream(self, search):
1894
if (self.from_repository._fallback_repositories and
1895
self.to_format._fetch_order == 'topological'):
1896
return self._real_stream(self.from_repository, search)
1899
repos = [self.from_repository]
1905
repos.extend(repo._fallback_repositories)
1906
sources.append(repo)
1907
return self.missing_parents_chain(search, sources)
1909
def get_stream_for_missing_keys(self, missing_keys):
1910
self.from_repository._ensure_real()
1911
real_repo = self.from_repository._real_repository
1912
real_source = real_repo._get_source(self.to_format)
1913
return real_source.get_stream_for_missing_keys(missing_keys)
1915
def _real_stream(self, repo, search):
1916
"""Get a stream for search from repo.
1918
This never called RemoteStreamSource.get_stream, and is a heler
1919
for RemoteStreamSource._get_stream to allow getting a stream
1920
reliably whether fallback back because of old servers or trying
1921
to stream from a non-RemoteRepository (which the stacked support
1924
source = repo._get_source(self.to_format)
1925
if isinstance(source, RemoteStreamSource):
1927
source = repo._real_repository._get_source(self.to_format)
1928
return source.get_stream(search)
1930
def _get_stream(self, repo, search):
1931
"""Core worker to get a stream from repo for search.
1933
This is used by both get_stream and the stacking support logic. It
1934
deliberately gets a stream for repo which does not need to be
1935
self.from_repository. In the event that repo is not Remote, or
1936
cannot do a smart stream, a fallback is made to the generic
1937
repository._get_stream() interface, via self._real_stream.
1939
In the event of stacking, streams from _get_stream will not
1940
contain all the data for search - this is normal (see get_stream).
1942
:param repo: A repository.
1943
:param search: A search.
1945
# Fallbacks may be non-smart
1946
if not isinstance(repo, RemoteRepository):
1947
return self._real_stream(repo, search)
1948
client = repo._client
1949
medium = client._medium
1950
path = repo.bzrdir._path_for_remote_call(client)
1951
search_bytes = repo._serialise_search_result(search)
1952
args = (path, self.to_format.network_name())
1954
('Repository.get_stream_1.19', (1, 19)),
1955
('Repository.get_stream', (1, 13))]
1957
for verb, version in candidate_verbs:
1958
if medium._is_remote_before(version):
1961
response = repo._call_with_body_bytes_expecting_body(
1962
verb, args, search_bytes)
1963
except errors.UnknownSmartMethod:
1964
medium._remember_remote_is_before(version)
1966
response_tuple, response_handler = response
1970
return self._real_stream(repo, search)
1971
if response_tuple[0] != 'ok':
1972
raise errors.UnexpectedSmartServerResponse(response_tuple)
1973
byte_stream = response_handler.read_streamed_body()
1974
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1975
if src_format.network_name() != repo._format.network_name():
1976
raise AssertionError(
1977
"Mismatched RemoteRepository and stream src %r, %r" % (
1978
src_format.network_name(), repo._format.network_name()))
1981
def missing_parents_chain(self, search, sources):
1982
"""Chain multiple streams together to handle stacking.
1984
:param search: The overall search to satisfy with streams.
1985
:param sources: A list of Repository objects to query.
1987
self.from_serialiser = self.from_repository._format._serializer
1988
self.seen_revs = set()
1989
self.referenced_revs = set()
1990
# If there are heads in the search, or the key count is > 0, we are not
1992
while not search.is_empty() and len(sources) > 1:
1993
source = sources.pop(0)
1994
stream = self._get_stream(source, search)
1995
for kind, substream in stream:
1996
if kind != 'revisions':
1997
yield kind, substream
1999
yield kind, self.missing_parents_rev_handler(substream)
2000
search = search.refine(self.seen_revs, self.referenced_revs)
2001
self.seen_revs = set()
2002
self.referenced_revs = set()
2003
if not search.is_empty():
2004
for kind, stream in self._get_stream(sources[0], search):
2007
def missing_parents_rev_handler(self, substream):
2008
for content in substream:
2009
revision_bytes = content.get_bytes_as('fulltext')
2010
revision = self.from_serialiser.read_revision_from_string(
2012
self.seen_revs.add(content.key[-1])
2013
self.referenced_revs.update(revision.parent_ids)
2017
821
class RemoteBranchLockableFiles(LockableFiles):
2018
822
"""A 'LockableFiles' implementation that talks to a smart server.
2020
824
This is not a public interface class.
2033
837
self._dir_mode = None
2034
838
self._file_mode = None
841
"""'get' a remote path as per the LockableFiles interface.
843
:param path: the file to 'get'. If this is 'branch.conf', we do not
844
just retrieve a file, instead we ask the smart server to generate
845
a configuration for us - which is retrieved as an INI file.
847
if path == 'branch.conf':
848
path = self.bzrdir._path_for_remote_call(self._client)
849
response = self._client.call_expecting_body(
850
'Branch.get_config_file', path)
851
assert response[0][0] == 'ok', \
852
'unexpected response code %s' % (response[0],)
853
return StringIO(response[1].read_body_bytes())
856
return LockableFiles.get(self, path)
2037
859
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
861
def __eq__(self, other):
2047
return (isinstance(other, RemoteBranchFormat) and
862
return (isinstance(other, RemoteBranchFormat) and
2048
863
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
865
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)
866
return 'Remote BZR Branch'
868
def get_format_string(self):
869
return 'Remote BZR Branch'
871
def open(self, a_bzrdir):
872
assert isinstance(a_bzrdir, RemoteBzrDir)
873
return a_bzrdir.open_branch()
875
def initialize(self, a_bzrdir):
876
assert isinstance(a_bzrdir, RemoteBzrDir)
877
return a_bzrdir.create_branch()
2137
879
def supports_tags(self):
2138
880
# Remote branches might support tags, but we won't know until we
2139
881
# 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):
885
class RemoteBranch(branch.Branch):
2153
886
"""Branch stored on a server accessed by HPSS RPC.
2155
888
At the moment most operations are mapped down to simple file operations.
2158
891
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2159
_client=None, format=None, setup_stacking=True, name=None):
2160
893
"""Create a RemoteBranch instance.
2162
895
:param real_branch: An optional local implementation of the branch
2163
896
format, usually accessing the data via the VFS.
2164
897
: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
899
# We intentionally don't call the parent class's __init__, because it
2174
900
# will try to assign to self.tags, which is a property in this subclass.
2175
901
# And the parent's __init__ doesn't do much anyway.
902
self._revision_history_cache = None
2176
903
self.bzrdir = remote_bzrdir
2177
904
if _client is not None:
2178
905
self._client = _client
2180
self._client = remote_bzrdir._client
907
self._client = client._SmartClient(self.bzrdir._shared_medium)
2181
908
self.repository = remote_repository
2182
909
if real_branch is not None:
2183
910
self._real_branch = real_branch
2404
988
branch_token = token
2405
989
repo_token = self.repository.lock_write()
2406
990
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':
991
path = self.bzrdir._path_for_remote_call(self._client)
992
response = self._client.call('Branch.lock_write', path, branch_token,
994
if response[0] == 'ok':
995
ok, branch_token, repo_token = response
996
return branch_token, repo_token
997
elif response[0] == 'LockContention':
998
raise errors.LockContention('(remote lock)')
999
elif response[0] == 'TokenMismatch':
1000
raise errors.TokenMismatch(token, '(remote token)')
1001
elif response[0] == 'UnlockableTransport':
1002
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1003
elif response[0] == 'ReadOnlyError':
1004
raise errors.ReadOnlyError(self)
1005
elif response[0] == 'LockFailed':
1006
raise errors.LockFailed(response[1], response[2])
2412
1008
raise errors.UnexpectedSmartServerResponse(response)
2413
ok, branch_token, repo_token = response
2414
return branch_token, repo_token
2416
1010
def lock_write(self, token=None):
2417
1011
if not self._lock_mode:
2418
self._note_lock('w')
2419
# Lock the branch and repo in one remote call.
2420
1012
remote_tokens = self._remote_lock_write(token)
2421
1013
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)
1014
assert self._lock_token, 'Remote server did not return a token!'
1015
# TODO: We really, really, really don't want to call _ensure_real
1016
# here, but it's the easiest way to ensure coherency between the
1017
# state of the RemoteBranch and RemoteRepository objects and the
1018
# physical locks. If we don't materialise the real objects here,
1019
# then getting everything in the right state later is complex, so
1020
# for now we just do it the lazy way.
1021
# -- Andrew Bennetts, 2007-02-22.
2428
1023
if self._real_branch is not None:
2429
self._real_branch.lock_write(token=self._lock_token)
1024
self._real_branch.repository.lock_write(
1025
token=self._repo_lock_token)
1027
self._real_branch.lock_write(token=self._lock_token)
1029
self._real_branch.repository.unlock()
2430
1030
if token is not None:
2431
1031
self._leave_lock = True
1033
# XXX: this case seems to be unreachable; token cannot be None.
2433
1034
self._leave_lock = False
2434
1035
self._lock_mode = 'w'
2435
1036
self._lock_count = 1
2437
1038
raise errors.ReadOnlyTransaction
2439
1040
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
1041
# A token was given to lock_write, and we're relocking, so check
1042
# that the given token actually matches the one we already have.
2443
1043
if token != self._lock_token:
2444
1044
raise errors.TokenMismatch(token, self._lock_token)
2445
1045
self._lock_count += 1
2446
# Re-lock the repository too.
2447
self.repository.lock_write(self._repo_lock_token)
2448
return self._lock_token or None
1046
return self._lock_token
2450
1048
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)
1049
path = self.bzrdir._path_for_remote_call(self._client)
1050
response = self._client.call('Branch.unlock', path, branch_token,
2455
1052
if response == ('ok',):
2457
raise errors.UnexpectedSmartServerResponse(response)
1054
elif response[0] == 'TokenMismatch':
1055
raise errors.TokenMismatch(
1056
str((branch_token, repo_token)), '(remote tokens)')
1058
raise errors.UnexpectedSmartServerResponse(response)
2459
@only_raises(errors.LockNotHeld, errors.LockBroken)
2460
1060
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
1061
self._lock_count -= 1
1062
if not self._lock_count:
1063
self._clear_cached_state()
1064
mode = self._lock_mode
1065
self._lock_mode = None
1066
if self._real_branch is not None:
2486
1067
if not self._leave_lock:
2487
self._unlock(branch_token, repo_token)
2489
self.repository.unlock()
1068
# If this RemoteBranch will remove the physical lock for the
1069
# repository, make sure the _real_branch doesn't do it
1070
# first. (Because the _real_branch's repository is set to
1071
# be the RemoteRepository.)
1072
self._real_branch.repository.leave_lock_in_place()
1073
self._real_branch.unlock()
1075
# Only write-locked branched need to make a remote method call
1076
# to perfom the unlock.
1078
assert self._lock_token, 'Locked, but no token!'
1079
branch_token = self._lock_token
1080
repo_token = self._repo_lock_token
1081
self._lock_token = None
1082
self._repo_lock_token = None
1083
if not self._leave_lock:
1084
self._unlock(branch_token, repo_token)
2491
1086
def break_lock(self):
2492
1087
self._ensure_real()
2493
1088
return self._real_branch.break_lock()
2495
1090
def leave_lock_in_place(self):
2496
if not self._lock_token:
2497
raise NotImplementedError(self.leave_lock_in_place)
2498
1091
self._leave_lock = True
2500
1093
def dont_leave_lock_in_place(self):
2501
if not self._lock_token:
2502
raise NotImplementedError(self.dont_leave_lock_in_place)
2503
1094
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,))
1096
def last_revision_info(self):
1097
"""See Branch.last_revision_info()."""
1098
path = self.bzrdir._path_for_remote_call(self._client)
1099
response = self._client.call('Branch.last_revision_info', path)
1100
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
2527
1101
revno = int(response[1])
2528
1102
last_revision = response[2]
2529
1103
return (revno, last_revision)
2531
1105
def _gen_revision_history(self):
2532
1106
"""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')
1107
path = self.bzrdir._path_for_remote_call(self._client)
1108
response = self._client.call_expecting_body(
1109
'Branch.revision_history', path)
1110
assert response[0][0] == 'ok', ('unexpected response code %s'
1112
result = response[1].read_body_bytes().split('\x00')
2541
1113
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
1117
@needs_write_lock
2593
1118
def set_revision_history(self, rev_history):
2594
1119
# Send just the tip revision of the history; the server will generate
2595
1120
# the full history from that. If the revision doesn't exist in this
2596
1121
# branch, NoSuchRevision will be raised.
1122
path = self.bzrdir._path_for_remote_call(self._client)
2597
1123
if rev_history == []:
2598
1124
rev_id = 'null:'
2600
1126
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)
1127
self._clear_cached_state()
1128
response = self._client.call('Branch.set_last_revision',
1129
path, self._lock_token, self._repo_lock_token, rev_id)
1130
if response[0] == 'NoSuchRevision':
1131
raise NoSuchRevision(self, rev_id)
1133
assert response == ('ok',), (
1134
'unexpected response code %r' % (response,))
2604
1135
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)
1137
def get_parent(self):
1139
return self._real_branch.get_parent()
1141
def set_parent(self, url):
1143
return self._real_branch.set_parent(url)
1145
def get_config(self):
1146
return RemoteBranchConfig(self)
1148
def sprout(self, to_bzrdir, revision_id=None):
1149
# Like Branch.sprout, except that it sprouts a branch in the default
1150
# format, because RemoteBranches can't be created at arbitrary URLs.
1151
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1152
# to_bzrdir.create_branch...
1153
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1154
self.copy_content_into(result, revision_id=revision_id)
1155
result.set_parent(self.bzrdir.root_transport.base)
2647
1158
@needs_write_lock
2648
1159
def pull(self, source, overwrite=False, stop_revision=None,
2650
self._clear_cached_state_of_remote_branch_only()
1161
# FIXME: This asks the real branch to run the hooks, which means
1162
# they're called with the wrong target branch parameter.
1163
# The test suite specifically allows this at present but it should be
1164
# fixed. It should get a _override_hook_target branch,
1165
# as push does. -- mbp 20070405
2651
1166
self._ensure_real()
2652
return self._real_branch.pull(
1167
self._real_branch.pull(
2653
1168
source, overwrite=overwrite, stop_revision=stop_revision,
2654
_override_hook_target=self, **kwargs)
2656
1171
@needs_read_lock
2657
1172
def push(self, target, overwrite=False, stop_revision=None):
2663
1178
def is_locked(self):
2664
1179
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
1181
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)
1183
self._clear_cached_state()
1184
return self._real_branch.set_last_revision_info(revno, revision_id)
2699
1186
def generate_revision_history(self, revision_id, last_rev=None,
2700
1187
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))
1189
return self._real_branch.generate_revision_history(
1190
revision_id, last_rev=last_rev, other_branch=other_branch)
1195
return self._real_branch.tags
2714
1197
def set_push_location(self, location):
2715
1198
self._ensure_real()
2716
1199
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
1201
def update_revisions(self, other, stop_revision=None):
1203
return self._real_branch.update_revisions(
1204
other, stop_revision=stop_revision)
1207
class RemoteBranchConfig(BranchConfig):
1210
self.branch._ensure_real()
1211
return self.branch._real_branch.get_config().username()
1213
def _get_branch_data_config(self):
1214
self.branch._ensure_real()
1215
if self._branch_data_config is None:
1216
self._branch_data_config = TreeConfig(self.branch._real_branch)
1217
return self._branch_data_config
2833
1220
def _extract_tar(tar, to_dir):
2838
1225
for tarinfo in tar:
2839
1226
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)