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 (
31
revision as _mod_revision,
34
from bzrlib.branch import BranchReferenceFormat
28
from bzrlib.branch import Branch, BranchReferenceFormat
35
29
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
36
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
37
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
41
33
from bzrlib.lockable_files import LockableFiles
42
from bzrlib.smart import client, vfs, repository as smart_repo
43
from bzrlib.revision import ensure_null, NULL_REVISION
44
from bzrlib.trace import mutter, note, warning
47
class _RpcHelper(object):
48
"""Mixin class that helps with issuing RPCs."""
50
def _call(self, method, *args, **err_context):
52
return self._client.call(method, *args)
53
except errors.ErrorFromSmartServer, err:
54
self._translate_error(err, **err_context)
56
def _call_expecting_body(self, method, *args, **err_context):
58
return self._client.call_expecting_body(method, *args)
59
except errors.ErrorFromSmartServer, err:
60
self._translate_error(err, **err_context)
62
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
64
return self._client.call_with_body_bytes(method, args, body_bytes)
65
except errors.ErrorFromSmartServer, err:
66
self._translate_error(err, **err_context)
68
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
71
return self._client.call_with_body_bytes_expecting_body(
72
method, args, body_bytes)
73
except errors.ErrorFromSmartServer, err:
74
self._translate_error(err, **err_context)
77
def response_tuple_to_repo_format(response):
78
"""Convert a response tuple describing a repository format to a format."""
79
format = RemoteRepositoryFormat()
80
format._rich_root_data = (response[0] == 'yes')
81
format._supports_tree_reference = (response[1] == 'yes')
82
format._supports_external_lookups = (response[2] == 'yes')
83
format._network_name = response[3]
34
from bzrlib.revision import NULL_REVISION
35
from bzrlib.smart import client, vfs
36
from bzrlib.trace import note
87
38
# Note: RemoteBzrDirFormat is in bzrdir.py
89
class RemoteBzrDir(BzrDir, _RpcHelper):
40
class RemoteBzrDir(BzrDir):
90
41
"""Control directory on a remote server, accessed via bzr:// or similar."""
92
def __init__(self, transport, format, _client=None, _force_probe=False):
43
def __init__(self, transport, _client=None):
93
44
"""Construct a RemoteBzrDir.
95
46
:param _client: Private parameter for testing. Disables probing and the
96
47
use of a real bzrdir.
98
BzrDir.__init__(self, transport, format)
49
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
99
50
# this object holds a delegated bzrdir that uses file-level operations
100
51
# to talk to the other side
101
52
self._real_bzrdir = None
102
self._has_working_tree = None
103
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
104
# create_branch for details.
105
self._next_open_branch_result = None
107
54
if _client is None:
108
medium = transport.get_smart_medium()
109
self._client = client._SmartClient(medium)
55
self._shared_medium = transport.get_shared_medium()
56
self._client = client._SmartClient(self._shared_medium)
111
58
self._client = _client
117
def _probe_bzrdir(self):
118
medium = self._client._medium
59
self._shared_medium = None
119
62
path = self._path_for_remote_call(self._client)
120
if medium._is_remote_before((2, 1)):
124
self._rpc_open_2_1(path)
126
except errors.UnknownSmartMethod:
127
medium._remember_remote_is_before((2, 1))
130
def _rpc_open_2_1(self, path):
131
response = self._call('BzrDir.open_2.1', path)
132
if response == ('no',):
133
raise errors.NotBranchError(path=self.root_transport.base)
134
elif response[0] == 'yes':
135
if response[1] == 'yes':
136
self._has_working_tree = True
137
elif response[1] == 'no':
138
self._has_working_tree = False
140
raise errors.UnexpectedSmartServerResponse(response)
142
raise errors.UnexpectedSmartServerResponse(response)
144
def _rpc_open(self, path):
145
response = self._call('BzrDir.open', path)
63
response = self._client.call('BzrDir.open', path)
146
64
if response not in [('yes',), ('no',)]:
147
65
raise errors.UnexpectedSmartServerResponse(response)
148
66
if response == ('no',):
149
raise errors.NotBranchError(path=self.root_transport.base)
67
raise errors.NotBranchError(path=transport.base)
151
69
def _ensure_real(self):
152
70
"""Ensure that there is a _real_bzrdir set.
154
72
Used before calls to self._real_bzrdir.
156
74
if not self._real_bzrdir:
157
if 'hpssvfs' in debug.debug_flags:
159
warning('VFS BzrDir access triggered\n%s',
160
''.join(traceback.format_stack()))
161
75
self._real_bzrdir = BzrDir.open_from_transport(
162
76
self.root_transport, _server_formats=False)
163
self._format._network_name = \
164
self._real_bzrdir._format.network_name()
166
def _translate_error(self, err, **context):
167
_translate_error(err, bzrdir=self, **context)
169
def break_lock(self):
170
# Prevent aliasing problems in the next_open_branch_result cache.
171
# See create_branch for rationale.
172
self._next_open_branch_result = None
173
return BzrDir.break_lock(self)
175
def _vfs_cloning_metadir(self, require_stacking=False):
177
return self._real_bzrdir.cloning_metadir(
178
require_stacking=require_stacking)
180
def cloning_metadir(self, require_stacking=False):
181
medium = self._client._medium
182
if medium._is_remote_before((1, 13)):
183
return self._vfs_cloning_metadir(require_stacking=require_stacking)
184
verb = 'BzrDir.cloning_metadir'
189
path = self._path_for_remote_call(self._client)
191
response = self._call(verb, path, stacking)
192
except errors.UnknownSmartMethod:
193
medium._remember_remote_is_before((1, 13))
194
return self._vfs_cloning_metadir(require_stacking=require_stacking)
195
except errors.UnknownErrorFromSmartServer, err:
196
if err.error_tuple != ('BranchReference',):
198
# We need to resolve the branch reference to determine the
199
# cloning_metadir. This causes unnecessary RPCs to open the
200
# referenced branch (and bzrdir, etc) but only when the caller
201
# didn't already resolve the branch reference.
202
referenced_branch = self.open_branch()
203
return referenced_branch.bzrdir.cloning_metadir()
204
if len(response) != 3:
205
raise errors.UnexpectedSmartServerResponse(response)
206
control_name, repo_name, branch_info = response
207
if len(branch_info) != 2:
208
raise errors.UnexpectedSmartServerResponse(response)
209
branch_ref, branch_name = branch_info
210
format = bzrdir.network_format_registry.get(control_name)
212
format.repository_format = repository.network_format_registry.get(
214
if branch_ref == 'ref':
215
# XXX: we need possible_transports here to avoid reopening the
216
# connection to the referenced location
217
ref_bzrdir = BzrDir.open(branch_name)
218
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
219
format.set_branch_format(branch_format)
220
elif branch_ref == 'branch':
222
format.set_branch_format(
223
branch.network_format_registry.get(branch_name))
225
raise errors.UnexpectedSmartServerResponse(response)
228
78
def create_repository(self, shared=False):
229
# as per meta1 formats - just delegate to the format object which may
231
result = self._format.repository_format.initialize(self, shared)
232
if not isinstance(result, RemoteRepository):
233
return self.open_repository()
237
def destroy_repository(self):
238
"""See BzrDir.destroy_repository"""
239
79
self._ensure_real()
240
self._real_bzrdir.destroy_repository()
80
self._real_bzrdir.create_repository(shared=shared)
81
return self.open_repository()
242
83
def create_branch(self):
243
# as per meta1 formats - just delegate to the format object which may
245
real_branch = self._format.get_branch_format().initialize(self)
246
if not isinstance(real_branch, RemoteBranch):
247
result = RemoteBranch(self, self.find_repository(), real_branch)
250
# BzrDir.clone_on_transport() uses the result of create_branch but does
251
# not return it to its callers; we save approximately 8% of our round
252
# trips by handing the branch we created back to the first caller to
253
# open_branch rather than probing anew. Long term we need a API in
254
# bzrdir that doesn't discard result objects (like result_branch).
256
self._next_open_branch_result = result
259
def destroy_branch(self):
260
"""See BzrDir.destroy_branch"""
261
84
self._ensure_real()
262
self._real_bzrdir.destroy_branch()
263
self._next_open_branch_result = None
85
real_branch = self._real_bzrdir.create_branch()
86
return RemoteBranch(self, self.find_repository(), real_branch)
265
def create_workingtree(self, revision_id=None, from_branch=None):
88
def create_workingtree(self, revision_id=None):
266
89
raise errors.NotLocalUrl(self.transport.base)
268
91
def find_branch_format(self):
276
99
def get_branch_reference(self):
277
100
"""See BzrDir.get_branch_reference()."""
278
response = self._get_branch_reference()
279
if response[0] == 'ref':
284
def _get_branch_reference(self):
285
101
path = self._path_for_remote_call(self._client)
286
medium = self._client._medium
287
if not medium._is_remote_before((1, 13)):
289
response = self._call('BzrDir.open_branchV2', path)
290
if response[0] not in ('ref', 'branch'):
291
raise errors.UnexpectedSmartServerResponse(response)
293
except errors.UnknownSmartMethod:
294
medium._remember_remote_is_before((1, 13))
295
response = self._call('BzrDir.open_branch', path)
296
if response[0] != 'ok':
102
response = self._client.call('BzrDir.open_branch', path)
103
if response[0] == 'ok':
104
if response[1] == '':
105
# branch at this location.
108
# a branch reference, use the existing BranchReference logic.
110
elif response == ('nobranch',):
111
raise errors.NotBranchError(path=self.root_transport.base)
297
113
raise errors.UnexpectedSmartServerResponse(response)
298
if response[1] != '':
299
return ('ref', response[1])
115
def open_branch(self, _unsupported=False):
116
assert _unsupported == False, 'unsupported flag support not implemented yet.'
117
reference_url = self.get_branch_reference()
118
if reference_url is None:
119
# branch at this location.
120
return RemoteBranch(self, self.find_repository())
301
return ('branch', '')
303
def _get_tree_branch(self):
304
"""See BzrDir._get_tree_branch()."""
305
return None, self.open_branch()
307
def open_branch(self, _unsupported=False, ignore_fallbacks=False):
309
raise NotImplementedError('unsupported flag support not implemented yet.')
310
if self._next_open_branch_result is not None:
311
# See create_branch for details.
312
result = self._next_open_branch_result
313
self._next_open_branch_result = None
315
response = self._get_branch_reference()
316
if response[0] == 'ref':
317
122
# a branch reference, use the existing BranchReference logic.
318
123
format = BranchReferenceFormat()
319
return format.open(self, _found=True, location=response[1],
320
ignore_fallbacks=ignore_fallbacks)
321
branch_format_name = response[1]
322
if not branch_format_name:
323
branch_format_name = None
324
format = RemoteBranchFormat(network_name=branch_format_name)
325
return RemoteBranch(self, self.find_repository(), format=format,
326
setup_stacking=not ignore_fallbacks)
328
def _open_repo_v1(self, path):
329
verb = 'BzrDir.find_repository'
330
response = self._call(verb, path)
331
if response[0] != 'ok':
332
raise errors.UnexpectedSmartServerResponse(response)
333
# servers that only support the v1 method don't support external
336
repo = self._real_bzrdir.open_repository()
337
response = response + ('no', repo._format.network_name())
338
return response, repo
340
def _open_repo_v2(self, path):
341
verb = 'BzrDir.find_repositoryV2'
342
response = self._call(verb, path)
343
if response[0] != 'ok':
344
raise errors.UnexpectedSmartServerResponse(response)
346
repo = self._real_bzrdir.open_repository()
347
response = response + (repo._format.network_name(),)
348
return response, repo
350
def _open_repo_v3(self, path):
351
verb = 'BzrDir.find_repositoryV3'
352
medium = self._client._medium
353
if medium._is_remote_before((1, 13)):
354
raise errors.UnknownSmartMethod(verb)
356
response = self._call(verb, path)
357
except errors.UnknownSmartMethod:
358
medium._remember_remote_is_before((1, 13))
360
if response[0] != 'ok':
361
raise errors.UnexpectedSmartServerResponse(response)
362
return response, None
124
return format.open(self, _found=True, location=reference_url)
364
126
def open_repository(self):
365
127
path = self._path_for_remote_call(self._client)
367
for probe in [self._open_repo_v3, self._open_repo_v2,
370
response, real_repo = probe(path)
372
except errors.UnknownSmartMethod:
375
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
376
if response[0] != 'ok':
377
raise errors.UnexpectedSmartServerResponse(response)
378
if len(response) != 6:
379
raise SmartProtocolError('incorrect response length %s' % (response,))
128
response = self._client.call('BzrDir.find_repository', path)
129
assert response[0] in ('ok', 'norepository'), \
130
'unexpected response code %s' % (response,)
131
if response[0] == 'norepository':
132
raise errors.NoRepositoryPresent(self)
133
assert len(response) == 4, 'incorrect response length %s' % (response,)
380
134
if response[1] == '':
381
# repo is at this dir.
382
format = response_tuple_to_repo_format(response[2:])
383
# Used to support creating a real format instance when needed.
384
format._creating_bzrdir = self
385
remote_repo = RemoteRepository(self, format)
386
format._creating_repo = remote_repo
387
if real_repo is not None:
388
remote_repo._set_real_repository(real_repo)
135
format = RemoteRepositoryFormat()
136
format.rich_root_data = (response[2] == 'yes')
137
format.supports_tree_reference = (response[3] == 'yes')
138
return RemoteRepository(self, format)
391
140
raise errors.NoRepositoryPresent(self)
393
def has_workingtree(self):
394
if self._has_working_tree is None:
396
self._has_working_tree = self._real_bzrdir.has_workingtree()
397
return self._has_working_tree
399
142
def open_workingtree(self, recommend_upgrade=True):
400
if self.has_workingtree():
144
if self._real_bzrdir.has_workingtree():
401
145
raise errors.NotLocalUrl(self.root_transport)
403
147
raise errors.NoWorkingTree(self.root_transport.base)
445
182
Instances of this repository are represented by RemoteRepository
448
The RemoteRepositoryFormat is parameterized during construction
185
The RemoteRepositoryFormat is parameterised during construction
449
186
to reflect the capabilities of the real, remote format. Specifically
450
187
the attributes rich_root_data and supports_tree_reference are set
451
188
on a per instance basis, and are not set (and should not be) at
454
:ivar _custom_format: If set, a specific concrete repository format that
455
will be used when initializing a repository with this
456
RemoteRepositoryFormat.
457
:ivar _creating_repo: If set, the repository object that this
458
RemoteRepositoryFormat was created for: it can be called into
459
to obtain data like the network name.
462
_matchingbzrdir = RemoteBzrDirFormat()
465
repository.RepositoryFormat.__init__(self)
466
self._custom_format = None
467
self._network_name = None
468
self._creating_bzrdir = None
469
self._supports_chks = None
470
self._supports_external_lookups = None
471
self._supports_tree_reference = None
472
self._rich_root_data = None
475
return "%s(_network_name=%r)" % (self.__class__.__name__,
479
def fast_deltas(self):
481
return self._custom_format.fast_deltas
484
def rich_root_data(self):
485
if self._rich_root_data is None:
487
self._rich_root_data = self._custom_format.rich_root_data
488
return self._rich_root_data
491
def supports_chks(self):
492
if self._supports_chks is None:
494
self._supports_chks = self._custom_format.supports_chks
495
return self._supports_chks
498
def supports_external_lookups(self):
499
if self._supports_external_lookups is None:
501
self._supports_external_lookups = \
502
self._custom_format.supports_external_lookups
503
return self._supports_external_lookups
506
def supports_tree_reference(self):
507
if self._supports_tree_reference is None:
509
self._supports_tree_reference = \
510
self._custom_format.supports_tree_reference
511
return self._supports_tree_reference
513
def _vfs_initialize(self, a_bzrdir, shared):
514
"""Helper for common code in initialize."""
515
if self._custom_format:
516
# Custom format requested
517
result = self._custom_format.initialize(a_bzrdir, shared=shared)
518
elif self._creating_bzrdir is not None:
519
# Use the format that the repository we were created to back
521
prior_repo = self._creating_bzrdir.open_repository()
522
prior_repo._ensure_real()
523
result = prior_repo._real_repository._format.initialize(
524
a_bzrdir, shared=shared)
526
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
527
# support remote initialization.
528
# We delegate to a real object at this point (as RemoteBzrDir
529
# delegate to the repository format which would lead to infinite
530
# recursion if we just called a_bzrdir.create_repository.
531
a_bzrdir._ensure_real()
532
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
533
if not isinstance(result, RemoteRepository):
534
return self.open(a_bzrdir)
192
_matchingbzrdir = RemoteBzrDirFormat
538
194
def initialize(self, a_bzrdir, shared=False):
539
# Being asked to create on a non RemoteBzrDir:
540
if not isinstance(a_bzrdir, RemoteBzrDir):
541
return self._vfs_initialize(a_bzrdir, shared)
542
medium = a_bzrdir._client._medium
543
if medium._is_remote_before((1, 13)):
544
return self._vfs_initialize(a_bzrdir, shared)
545
# Creating on a remote bzr dir.
546
# 1) get the network name to use.
547
if self._custom_format:
548
network_name = self._custom_format.network_name()
549
elif self._network_name:
550
network_name = self._network_name
552
# Select the current bzrlib default and ask for that.
553
reference_bzrdir_format = bzrdir.format_registry.get('default')()
554
reference_format = reference_bzrdir_format.repository_format
555
network_name = reference_format.network_name()
556
# 2) try direct creation via RPC
557
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
558
verb = 'BzrDir.create_repository'
564
response = a_bzrdir._call(verb, path, network_name, shared_str)
565
except errors.UnknownSmartMethod:
566
# Fallback - use vfs methods
567
medium._remember_remote_is_before((1, 13))
568
return self._vfs_initialize(a_bzrdir, shared)
570
# Turn the response into a RemoteRepository object.
571
format = response_tuple_to_repo_format(response[1:])
572
# Used to support creating a real format instance when needed.
573
format._creating_bzrdir = a_bzrdir
574
remote_repo = RemoteRepository(a_bzrdir, format)
575
format._creating_repo = remote_repo
195
assert isinstance(a_bzrdir, RemoteBzrDir), \
196
'%r is not a RemoteBzrDir' % (a_bzrdir,)
197
return a_bzrdir.create_repository(shared=shared)
578
199
def open(self, a_bzrdir):
579
if not isinstance(a_bzrdir, RemoteBzrDir):
580
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
200
assert isinstance(a_bzrdir, RemoteBzrDir)
581
201
return a_bzrdir.open_repository()
583
def _ensure_real(self):
584
if self._custom_format is None:
585
self._custom_format = repository.network_format_registry.get(
589
def _fetch_order(self):
591
return self._custom_format._fetch_order
594
def _fetch_uses_deltas(self):
596
return self._custom_format._fetch_uses_deltas
599
def _fetch_reconcile(self):
601
return self._custom_format._fetch_reconcile
603
203
def get_format_description(self):
605
return 'Remote: ' + self._custom_format.get_format_description()
204
return 'bzr remote repository'
607
206
def __eq__(self, other):
608
return self.__class__ is other.__class__
610
def network_name(self):
611
if self._network_name:
612
return self._network_name
613
self._creating_repo._ensure_real()
614
return self._creating_repo._real_repository._format.network_name()
617
def pack_compresses(self):
619
return self._custom_format.pack_compresses
622
def _serializer(self):
624
return self._custom_format._serializer
627
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
207
return self.__class__ == other.__class__
209
def check_conversion_target(self, target_format):
210
if self.rich_root_data and not target_format.rich_root_data:
211
raise errors.BadConversionTarget(
212
'Does not support rich root data.', target_format)
213
if (self.supports_tree_reference and
214
not getattr(target_format, 'supports_tree_reference', False)):
215
raise errors.BadConversionTarget(
216
'Does not support nested trees', target_format)
219
class RemoteRepository(object):
628
220
"""Repository accessed over rpc.
630
222
For the moment most operations are performed using local transport-backed
713
271
self._ensure_real()
714
272
return self._real_repository.commit_write_group()
716
def resume_write_group(self, tokens):
718
return self._real_repository.resume_write_group(tokens)
720
def suspend_write_group(self):
722
return self._real_repository.suspend_write_group()
724
def get_missing_parent_inventories(self, check_for_missing_texts=True):
726
return self._real_repository.get_missing_parent_inventories(
727
check_for_missing_texts=check_for_missing_texts)
729
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
731
return self._real_repository.get_rev_id_for_revno(
734
def get_rev_id_for_revno(self, revno, known_pair):
735
"""See Repository.get_rev_id_for_revno."""
736
path = self.bzrdir._path_for_remote_call(self._client)
738
if self._client._medium._is_remote_before((1, 17)):
739
return self._get_rev_id_for_revno_vfs(revno, known_pair)
740
response = self._call(
741
'Repository.get_rev_id_for_revno', path, revno, known_pair)
742
except errors.UnknownSmartMethod:
743
self._client._medium._remember_remote_is_before((1, 17))
744
return self._get_rev_id_for_revno_vfs(revno, known_pair)
745
if response[0] == 'ok':
746
return True, response[1]
747
elif response[0] == 'history-incomplete':
748
known_pair = response[1:3]
749
for fallback in self._fallback_repositories:
750
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
755
# Not found in any fallbacks
756
return False, known_pair
758
raise errors.UnexpectedSmartServerResponse(response)
760
274
def _ensure_real(self):
761
275
"""Ensure that there is a _real_repository set.
763
277
Used before calls to self._real_repository.
765
Note that _ensure_real causes many roundtrips to the server which are
766
not desirable, and prevents the use of smart one-roundtrip RPC's to
767
perform complex operations (such as accessing parent data, streaming
768
revisions etc). Adding calls to _ensure_real should only be done when
769
bringing up new functionality, adding fallbacks for smart methods that
770
require a fallback path, and never to replace an existing smart method
771
invocation. If in doubt chat to the bzr network team.
773
if self._real_repository is None:
774
if 'hpssvfs' in debug.debug_flags:
776
warning('VFS Repository access triggered\n%s',
777
''.join(traceback.format_stack()))
778
self._unstacked_provider.missing_keys.clear()
279
if not self._real_repository:
779
280
self.bzrdir._ensure_real()
780
self._set_real_repository(
781
self.bzrdir._real_bzrdir.open_repository())
783
def _translate_error(self, err, **context):
784
self.bzrdir._translate_error(err, repository=self, **context)
786
def find_text_key_references(self):
787
"""Find the text key references within the repository.
789
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
790
revision_ids. Each altered file-ids has the exact revision_ids that
791
altered it listed explicitly.
792
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
793
to whether they were referred to by the inventory of the
794
revision_id that they contain. The inventory texts from all present
795
revision ids are assessed to generate this report.
798
return self._real_repository.find_text_key_references()
800
def _generate_text_key_index(self):
801
"""Generate a new text key index for the repository.
803
This is an expensive function that will take considerable time to run.
805
:return: A dict mapping (file_id, revision_id) tuples to a list of
806
parents, also (file_id, revision_id) tuples.
809
return self._real_repository._generate_text_key_index()
811
def _get_revision_graph(self, revision_id):
812
"""Private method for using with old (< 1.2) servers to fallback."""
281
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
282
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
284
def get_revision_graph(self, revision_id=None):
285
"""See Repository.get_revision_graph()."""
813
286
if revision_id is None:
815
elif revision.is_null(revision_id):
288
elif revision_id == NULL_REVISION:
818
291
path = self.bzrdir._path_for_remote_call(self._client)
819
response = self._call_expecting_body(
292
assert type(revision_id) is str
293
response = self._client.call_expecting_body(
820
294
'Repository.get_revision_graph', path, revision_id)
821
response_tuple, response_handler = response
822
if response_tuple[0] != 'ok':
823
raise errors.UnexpectedSmartServerResponse(response_tuple)
824
coded = response_handler.read_body_bytes()
826
# no revisions in this repository!
828
lines = coded.split('\n')
831
d = tuple(line.split())
832
revision_graph[d[0]] = d[1:]
834
return revision_graph
837
"""See Repository._get_sink()."""
838
return RemoteStreamSink(self)
840
def _get_source(self, to_format):
841
"""Return a source for streaming from this repository."""
842
return RemoteStreamSource(self, to_format)
295
if response[0][0] not in ['ok', 'nosuchrevision']:
296
raise errors.UnexpectedSmartServerResponse(response[0])
297
if response[0][0] == 'ok':
298
coded = response[1].read_body_bytes()
300
# no revisions in this repository!
302
lines = coded.split('\n')
305
d = tuple(line.split())
306
revision_graph[d[0]] = d[1:]
308
return revision_graph
310
response_body = response[1].read_body_bytes()
311
assert response_body == ''
312
raise NoSuchRevision(self, revision_id)
845
314
def has_revision(self, revision_id):
846
"""True if this repository has a copy of the revision."""
847
# Copy of bzrlib.repository.Repository.has_revision
848
return revision_id in self.has_revisions((revision_id,))
851
def has_revisions(self, revision_ids):
852
"""Probe to find out the presence of multiple revisions.
854
:param revision_ids: An iterable of revision_ids.
855
:return: A set of the revision_ids that were present.
857
# Copy of bzrlib.repository.Repository.has_revisions
858
parent_map = self.get_parent_map(revision_ids)
859
result = set(parent_map)
860
if _mod_revision.NULL_REVISION in revision_ids:
861
result.add(_mod_revision.NULL_REVISION)
864
def _has_same_fallbacks(self, other_repo):
865
"""Returns true if the repositories have the same fallbacks."""
866
# XXX: copied from Repository; it should be unified into a base class
867
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
868
my_fb = self._fallback_repositories
869
other_fb = other_repo._fallback_repositories
870
if len(my_fb) != len(other_fb):
872
for f, g in zip(my_fb, other_fb):
873
if not f.has_same_location(g):
315
"""See Repository.has_revision()."""
316
if revision_id is None:
317
# The null revision is always present.
319
path = self.bzrdir._path_for_remote_call(self._client)
320
response = self._client.call('Repository.has_revision', path, revision_id)
321
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
322
return response[0] == 'yes'
877
324
def has_same_location(self, other):
878
# TODO: Move to RepositoryBase and unify with the regular Repository
879
# one; unfortunately the tests rely on slightly different behaviour at
880
# present -- mbp 20090710
881
return (self.__class__ is other.__class__ and
325
return (self.__class__ == other.__class__ and
882
326
self.bzrdir.transport.base == other.bzrdir.transport.base)
884
328
def get_graph(self, other_repository=None):
885
329
"""Return the graph for this repository format"""
886
parents_provider = self._make_parents_provider(other_repository)
887
return graph.Graph(parents_provider)
330
return self._real_repository.get_graph(other_repository)
889
332
def gather_stats(self, revid=None, committers=None):
890
333
"""See Repository.gather_stats()."""
891
334
path = self.bzrdir._path_for_remote_call(self._client)
892
# revid can be None to indicate no revisions, not just NULL_REVISION
893
if revid is None or revision.is_null(revid):
335
if revid in (None, NULL_REVISION):
896
338
fmt_revid = revid
1178
533
# FIXME: It ought to be possible to call this without immediately
1179
534
# triggering _ensure_real. For now it's the easiest thing to do.
1180
535
self._ensure_real()
1181
real_repo = self._real_repository
1182
builder = real_repo.get_commit_builder(branch, parents,
536
builder = self._real_repository.get_commit_builder(branch, parents,
1183
537
config, timestamp=timestamp, timezone=timezone,
1184
538
committer=committer, revprops=revprops, revision_id=revision_id)
539
# Make the builder use this RemoteRepository rather than the real one.
540
builder.repository = self
1187
def add_fallback_repository(self, repository):
1188
"""Add a repository to use for looking up data not held locally.
1190
:param repository: A repository.
1192
if not self._format.supports_external_lookups:
1193
raise errors.UnstackableRepositoryFormat(
1194
self._format.network_name(), self.base)
1195
# We need to accumulate additional repositories here, to pass them in
1198
if self.is_locked():
1199
# We will call fallback.unlock() when we transition to the unlocked
1200
# state, so always add a lock here. If a caller passes us a locked
1201
# repository, they are responsible for unlocking it later.
1202
repository.lock_read()
1203
self._fallback_repositories.append(repository)
1204
# If self._real_repository was parameterised already (e.g. because a
1205
# _real_branch had its get_stacked_on_url method called), then the
1206
# repository to be added may already be in the _real_repositories list.
1207
if self._real_repository is not None:
1208
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1209
self._real_repository._fallback_repositories]
1210
if repository.bzrdir.root_transport.base not in fallback_locations:
1211
self._real_repository.add_fallback_repository(repository)
1213
544
def add_inventory(self, revid, inv, parents):
1214
545
self._ensure_real()
1215
546
return self._real_repository.add_inventory(revid, inv, parents)
1217
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1220
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1221
delta, new_revision_id, parents)
1223
549
def add_revision(self, rev_id, rev, inv=None, config=None):
1224
550
self._ensure_real()
1225
551
return self._real_repository.add_revision(
1249
576
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1251
578
def make_working_trees(self):
1252
"""See Repository.make_working_trees"""
579
"""RemoteRepositories never create working trees by default."""
582
def fetch(self, source, revision_id=None, pb=None):
1253
583
self._ensure_real()
1254
return self._real_repository.make_working_trees()
1256
def refresh_data(self):
1257
"""Re-read any data needed to to synchronise with disk.
1259
This method is intended to be called after another repository instance
1260
(such as one used by a smart server) has inserted data into the
1261
repository. It may not be called during a write group, but may be
1262
called at any other time.
1264
if self.is_in_write_group():
1265
raise errors.InternalBzrError(
1266
"May not refresh_data while in a write group.")
1267
if self._real_repository is not None:
1268
self._real_repository.refresh_data()
1270
def revision_ids_to_search_result(self, result_set):
1271
"""Convert a set of revision ids to a graph SearchResult."""
1272
result_parents = set()
1273
for parents in self.get_graph().get_parent_map(
1274
result_set).itervalues():
1275
result_parents.update(parents)
1276
included_keys = result_set.intersection(result_parents)
1277
start_keys = result_set.difference(included_keys)
1278
exclude_keys = result_parents.difference(result_set)
1279
result = graph.SearchResult(start_keys, exclude_keys,
1280
len(result_set), result_set)
1284
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1285
"""Return the revision ids that other has that this does not.
1287
These are returned in topological order.
1289
revision_id: only return revision ids included by revision_id.
1291
return repository.InterRepository.get(
1292
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1294
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1296
# No base implementation to use as RemoteRepository is not a subclass
1297
# of Repository; so this is a copy of Repository.fetch().
1298
if fetch_spec is not None and revision_id is not None:
1299
raise AssertionError(
1300
"fetch_spec and revision_id are mutually exclusive.")
1301
if self.is_in_write_group():
1302
raise errors.InternalBzrError(
1303
"May not fetch while in a write group.")
1304
# fast path same-url fetch operations
1305
if (self.has_same_location(source)
1306
and fetch_spec is None
1307
and self._has_same_fallbacks(source)):
1308
# check that last_revision is in 'from' and then return a
1310
if (revision_id is not None and
1311
not revision.is_null(revision_id)):
1312
self.get_revision(revision_id)
1314
# if there is no specific appropriate InterRepository, this will get
1315
# the InterRepository base class, which raises an
1316
# IncompatibleRepositories when asked to fetch.
1317
inter = repository.InterRepository.get(source, self)
1318
return inter.fetch(revision_id=revision_id, pb=pb,
1319
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
584
return self._real_repository.fetch(
585
source, revision_id=revision_id, pb=pb)
1321
587
def create_bundle(self, target, base, fileobj, format=None):
1322
588
self._ensure_real()
1323
589
self._real_repository.create_bundle(target, base, fileobj, format)
592
def control_weaves(self):
594
return self._real_repository.control_weaves
1325
596
@needs_read_lock
1326
597
def get_ancestry(self, revision_id, topo_sorted=True):
1327
598
self._ensure_real()
1328
599
return self._real_repository.get_ancestry(revision_id, topo_sorted)
602
def get_inventory_weave(self):
604
return self._real_repository.get_inventory_weave()
1330
606
def fileids_altered_by_revision_ids(self, revision_ids):
1331
607
self._ensure_real()
1332
608
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1334
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1336
return self._real_repository._get_versioned_file_checker(
1337
revisions, revision_versions_cache)
1339
def iter_files_bytes(self, desired_files):
1340
"""See Repository.iter_file_bytes.
1343
return self._real_repository.iter_files_bytes(desired_files)
1345
def get_parent_map(self, revision_ids):
1346
"""See bzrlib.Graph.get_parent_map()."""
1347
return self._make_parents_provider().get_parent_map(revision_ids)
1349
def _get_parent_map_rpc(self, keys):
1350
"""Helper for get_parent_map that performs the RPC."""
1351
medium = self._client._medium
1352
if medium._is_remote_before((1, 2)):
1353
# We already found out that the server can't understand
1354
# Repository.get_parent_map requests, so just fetch the whole
1357
# Note that this reads the whole graph, when only some keys are
1358
# wanted. On this old server there's no way (?) to get them all
1359
# in one go, and the user probably will have seen a warning about
1360
# the server being old anyhow.
1361
rg = self._get_revision_graph(None)
1362
# There is an API discrepancy between get_parent_map and
1363
# get_revision_graph. Specifically, a "key:()" pair in
1364
# get_revision_graph just means a node has no parents. For
1365
# "get_parent_map" it means the node is a ghost. So fix up the
1366
# graph to correct this.
1367
# https://bugs.launchpad.net/bzr/+bug/214894
1368
# There is one other "bug" which is that ghosts in
1369
# get_revision_graph() are not returned at all. But we won't worry
1370
# about that for now.
1371
for node_id, parent_ids in rg.iteritems():
1372
if parent_ids == ():
1373
rg[node_id] = (NULL_REVISION,)
1374
rg[NULL_REVISION] = ()
1379
raise ValueError('get_parent_map(None) is not valid')
1380
if NULL_REVISION in keys:
1381
keys.discard(NULL_REVISION)
1382
found_parents = {NULL_REVISION:()}
1384
return found_parents
1387
# TODO(Needs analysis): We could assume that the keys being requested
1388
# from get_parent_map are in a breadth first search, so typically they
1389
# will all be depth N from some common parent, and we don't have to
1390
# have the server iterate from the root parent, but rather from the
1391
# keys we're searching; and just tell the server the keyspace we
1392
# already have; but this may be more traffic again.
1394
# Transform self._parents_map into a search request recipe.
1395
# TODO: Manage this incrementally to avoid covering the same path
1396
# repeatedly. (The server will have to on each request, but the less
1397
# work done the better).
1399
# Negative caching notes:
1400
# new server sends missing when a request including the revid
1401
# 'include-missing:' is present in the request.
1402
# missing keys are serialised as missing:X, and we then call
1403
# provider.note_missing(X) for-all X
1404
parents_map = self._unstacked_provider.get_cached_map()
1405
if parents_map is None:
1406
# Repository is not locked, so there's no cache.
1408
# start_set is all the keys in the cache
1409
start_set = set(parents_map)
1410
# result set is all the references to keys in the cache
1411
result_parents = set()
1412
for parents in parents_map.itervalues():
1413
result_parents.update(parents)
1414
stop_keys = result_parents.difference(start_set)
1415
# We don't need to send ghosts back to the server as a position to
1417
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1418
key_count = len(parents_map)
1419
if (NULL_REVISION in result_parents
1420
and NULL_REVISION in self._unstacked_provider.missing_keys):
1421
# If we pruned NULL_REVISION from the stop_keys because it's also
1422
# in our cache of "missing" keys we need to increment our key count
1423
# by 1, because the reconsitituted SearchResult on the server will
1424
# still consider NULL_REVISION to be an included key.
1426
included_keys = start_set.intersection(result_parents)
1427
start_set.difference_update(included_keys)
1428
recipe = ('manual', start_set, stop_keys, key_count)
1429
body = self._serialise_search_recipe(recipe)
1430
path = self.bzrdir._path_for_remote_call(self._client)
1432
if type(key) is not str:
1434
"key %r not a plain string" % (key,))
1435
verb = 'Repository.get_parent_map'
1436
args = (path, 'include-missing:') + tuple(keys)
1438
response = self._call_with_body_bytes_expecting_body(
1440
except errors.UnknownSmartMethod:
1441
# Server does not support this method, so get the whole graph.
1442
# Worse, we have to force a disconnection, because the server now
1443
# doesn't realise it has a body on the wire to consume, so the
1444
# only way to recover is to abandon the connection.
1446
'Server is too old for fast get_parent_map, reconnecting. '
1447
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1449
# To avoid having to disconnect repeatedly, we keep track of the
1450
# fact the server doesn't understand remote methods added in 1.2.
1451
medium._remember_remote_is_before((1, 2))
1452
# Recurse just once and we should use the fallback code.
1453
return self._get_parent_map_rpc(keys)
1454
response_tuple, response_handler = response
1455
if response_tuple[0] not in ['ok']:
1456
response_handler.cancel_read_body()
1457
raise errors.UnexpectedSmartServerResponse(response_tuple)
1458
if response_tuple[0] == 'ok':
1459
coded = bz2.decompress(response_handler.read_body_bytes())
1461
# no revisions found
1463
lines = coded.split('\n')
1466
d = tuple(line.split())
1468
revision_graph[d[0]] = d[1:]
1471
if d[0].startswith('missing:'):
1473
self._unstacked_provider.note_missing_key(revid)
1475
# no parents - so give the Graph result
1477
revision_graph[d[0]] = (NULL_REVISION,)
1478
return revision_graph
1480
610
@needs_read_lock
1481
611
def get_signature_text(self, revision_id):
1482
612
self._ensure_real()
1483
613
return self._real_repository.get_signature_text(revision_id)
1485
615
@needs_read_lock
616
def get_revision_graph_with_ghosts(self, revision_ids=None):
618
return self._real_repository.get_revision_graph_with_ghosts(
619
revision_ids=revision_ids)
1486
622
def get_inventory_xml(self, revision_id):
1487
623
self._ensure_real()
1488
624
return self._real_repository.get_inventory_xml(revision_id)
1648
728
def _serializer(self):
1649
return self._format._serializer
730
return self._real_repository._serializer
1651
732
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1652
733
self._ensure_real()
1653
734
return self._real_repository.store_revision_signature(
1654
735
gpg_strategy, plaintext, revision_id)
1656
def add_signature_text(self, revision_id, signature):
1658
return self._real_repository.add_signature_text(revision_id, signature)
1660
737
def has_signature_for_revision_id(self, revision_id):
1661
738
self._ensure_real()
1662
739
return self._real_repository.has_signature_for_revision_id(revision_id)
1664
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1666
return self._real_repository.item_keys_introduced_by(revision_ids,
1667
_files_pb=_files_pb)
1669
def revision_graph_can_have_wrong_parents(self):
1670
# The answer depends on the remote repo format.
1672
return self._real_repository.revision_graph_can_have_wrong_parents()
1674
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1676
return self._real_repository._find_inconsistent_revision_parents(
1679
def _check_for_inconsistent_revision_parents(self):
1681
return self._real_repository._check_for_inconsistent_revision_parents()
1683
def _make_parents_provider(self, other=None):
1684
providers = [self._unstacked_provider]
1685
if other is not None:
1686
providers.insert(0, other)
1687
providers.extend(r._make_parents_provider() for r in
1688
self._fallback_repositories)
1689
return graph.StackedParentsProvider(providers)
1691
def _serialise_search_recipe(self, recipe):
1692
"""Serialise a graph search recipe.
1694
:param recipe: A search recipe (start, stop, count).
1695
:return: Serialised bytes.
1697
start_keys = ' '.join(recipe[1])
1698
stop_keys = ' '.join(recipe[2])
1699
count = str(recipe[3])
1700
return '\n'.join((start_keys, stop_keys, count))
1702
def _serialise_search_result(self, search_result):
1703
if isinstance(search_result, graph.PendingAncestryResult):
1704
parts = ['ancestry-of']
1705
parts.extend(search_result.heads)
1707
recipe = search_result.get_recipe()
1708
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1709
return '\n'.join(parts)
1712
path = self.bzrdir._path_for_remote_call(self._client)
1714
response = self._call('PackRepository.autopack', path)
1715
except errors.UnknownSmartMethod:
1717
self._real_repository._pack_collection.autopack()
1720
if response[0] != 'ok':
1721
raise errors.UnexpectedSmartServerResponse(response)
1724
class RemoteStreamSink(repository.StreamSink):
1726
def _insert_real(self, stream, src_format, resume_tokens):
1727
self.target_repo._ensure_real()
1728
sink = self.target_repo._real_repository._get_sink()
1729
result = sink.insert_stream(stream, src_format, resume_tokens)
1731
self.target_repo.autopack()
1734
def insert_stream(self, stream, src_format, resume_tokens):
1735
target = self.target_repo
1736
target._unstacked_provider.missing_keys.clear()
1737
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1738
if target._lock_token:
1739
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1740
lock_args = (target._lock_token or '',)
1742
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1744
client = target._client
1745
medium = client._medium
1746
path = target.bzrdir._path_for_remote_call(client)
1747
# Probe for the verb to use with an empty stream before sending the
1748
# real stream to it. We do this both to avoid the risk of sending a
1749
# large request that is then rejected, and because we don't want to
1750
# implement a way to buffer, rewind, or restart the stream.
1752
for verb, required_version in candidate_calls:
1753
if medium._is_remote_before(required_version):
1756
# We've already done the probing (and set _is_remote_before) on
1757
# a previous insert.
1760
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1762
response = client.call_with_body_stream(
1763
(verb, path, '') + lock_args, byte_stream)
1764
except errors.UnknownSmartMethod:
1765
medium._remember_remote_is_before(required_version)
1771
return self._insert_real(stream, src_format, resume_tokens)
1772
self._last_inv_record = None
1773
self._last_substream = None
1774
if required_version < (1, 19):
1775
# Remote side doesn't support inventory deltas. Wrap the stream to
1776
# make sure we don't send any. If the stream contains inventory
1777
# deltas we'll interrupt the smart insert_stream request and
1779
stream = self._stop_stream_if_inventory_delta(stream)
1780
byte_stream = smart_repo._stream_to_byte_stream(
1782
resume_tokens = ' '.join(resume_tokens)
1783
response = client.call_with_body_stream(
1784
(verb, path, resume_tokens) + lock_args, byte_stream)
1785
if response[0][0] not in ('ok', 'missing-basis'):
1786
raise errors.UnexpectedSmartServerResponse(response)
1787
if self._last_substream is not None:
1788
# The stream included an inventory-delta record, but the remote
1789
# side isn't new enough to support them. So we need to send the
1790
# rest of the stream via VFS.
1791
self.target_repo.refresh_data()
1792
return self._resume_stream_with_vfs(response, src_format)
1793
if response[0][0] == 'missing-basis':
1794
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1795
resume_tokens = tokens
1796
return resume_tokens, set(missing_keys)
1798
self.target_repo.refresh_data()
1801
def _resume_stream_with_vfs(self, response, src_format):
1802
"""Resume sending a stream via VFS, first resending the record and
1803
substream that couldn't be sent via an insert_stream verb.
1805
if response[0][0] == 'missing-basis':
1806
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1807
# Ignore missing_keys, we haven't finished inserting yet
1810
def resume_substream():
1811
# Yield the substream that was interrupted.
1812
for record in self._last_substream:
1814
self._last_substream = None
1815
def resume_stream():
1816
# Finish sending the interrupted substream
1817
yield ('inventory-deltas', resume_substream())
1818
# Then simply continue sending the rest of the stream.
1819
for substream_kind, substream in self._last_stream:
1820
yield substream_kind, substream
1821
return self._insert_real(resume_stream(), src_format, tokens)
1823
def _stop_stream_if_inventory_delta(self, stream):
1824
"""Normally this just lets the original stream pass-through unchanged.
1826
However if any 'inventory-deltas' substream occurs it will stop
1827
streaming, and store the interrupted substream and stream in
1828
self._last_substream and self._last_stream so that the stream can be
1829
resumed by _resume_stream_with_vfs.
1832
stream_iter = iter(stream)
1833
for substream_kind, substream in stream_iter:
1834
if substream_kind == 'inventory-deltas':
1835
self._last_substream = substream
1836
self._last_stream = stream_iter
1839
yield substream_kind, substream
1842
class RemoteStreamSource(repository.StreamSource):
1843
"""Stream data from a remote server."""
1845
def get_stream(self, search):
1846
if (self.from_repository._fallback_repositories and
1847
self.to_format._fetch_order == 'topological'):
1848
return self._real_stream(self.from_repository, search)
1851
repos = [self.from_repository]
1857
repos.extend(repo._fallback_repositories)
1858
sources.append(repo)
1859
return self.missing_parents_chain(search, sources)
1861
def get_stream_for_missing_keys(self, missing_keys):
1862
self.from_repository._ensure_real()
1863
real_repo = self.from_repository._real_repository
1864
real_source = real_repo._get_source(self.to_format)
1865
return real_source.get_stream_for_missing_keys(missing_keys)
1867
def _real_stream(self, repo, search):
1868
"""Get a stream for search from repo.
1870
This never called RemoteStreamSource.get_stream, and is a heler
1871
for RemoteStreamSource._get_stream to allow getting a stream
1872
reliably whether fallback back because of old servers or trying
1873
to stream from a non-RemoteRepository (which the stacked support
1876
source = repo._get_source(self.to_format)
1877
if isinstance(source, RemoteStreamSource):
1879
source = repo._real_repository._get_source(self.to_format)
1880
return source.get_stream(search)
1882
def _get_stream(self, repo, search):
1883
"""Core worker to get a stream from repo for search.
1885
This is used by both get_stream and the stacking support logic. It
1886
deliberately gets a stream for repo which does not need to be
1887
self.from_repository. In the event that repo is not Remote, or
1888
cannot do a smart stream, a fallback is made to the generic
1889
repository._get_stream() interface, via self._real_stream.
1891
In the event of stacking, streams from _get_stream will not
1892
contain all the data for search - this is normal (see get_stream).
1894
:param repo: A repository.
1895
:param search: A search.
1897
# Fallbacks may be non-smart
1898
if not isinstance(repo, RemoteRepository):
1899
return self._real_stream(repo, search)
1900
client = repo._client
1901
medium = client._medium
1902
path = repo.bzrdir._path_for_remote_call(client)
1903
search_bytes = repo._serialise_search_result(search)
1904
args = (path, self.to_format.network_name())
1906
('Repository.get_stream_1.19', (1, 19)),
1907
('Repository.get_stream', (1, 13))]
1909
for verb, version in candidate_verbs:
1910
if medium._is_remote_before(version):
1913
response = repo._call_with_body_bytes_expecting_body(
1914
verb, args, search_bytes)
1915
except errors.UnknownSmartMethod:
1916
medium._remember_remote_is_before(version)
1918
response_tuple, response_handler = response
1922
return self._real_stream(repo, search)
1923
if response_tuple[0] != 'ok':
1924
raise errors.UnexpectedSmartServerResponse(response_tuple)
1925
byte_stream = response_handler.read_streamed_body()
1926
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1927
if src_format.network_name() != repo._format.network_name():
1928
raise AssertionError(
1929
"Mismatched RemoteRepository and stream src %r, %r" % (
1930
src_format.network_name(), repo._format.network_name()))
1933
def missing_parents_chain(self, search, sources):
1934
"""Chain multiple streams together to handle stacking.
1936
:param search: The overall search to satisfy with streams.
1937
:param sources: A list of Repository objects to query.
1939
self.from_serialiser = self.from_repository._format._serializer
1940
self.seen_revs = set()
1941
self.referenced_revs = set()
1942
# If there are heads in the search, or the key count is > 0, we are not
1944
while not search.is_empty() and len(sources) > 1:
1945
source = sources.pop(0)
1946
stream = self._get_stream(source, search)
1947
for kind, substream in stream:
1948
if kind != 'revisions':
1949
yield kind, substream
1951
yield kind, self.missing_parents_rev_handler(substream)
1952
search = search.refine(self.seen_revs, self.referenced_revs)
1953
self.seen_revs = set()
1954
self.referenced_revs = set()
1955
if not search.is_empty():
1956
for kind, stream in self._get_stream(sources[0], search):
1959
def missing_parents_rev_handler(self, substream):
1960
for content in substream:
1961
revision_bytes = content.get_bytes_as('fulltext')
1962
revision = self.from_serialiser.read_revision_from_string(
1964
self.seen_revs.add(content.key[-1])
1965
self.referenced_revs.update(revision.parent_ids)
1969
742
class RemoteBranchLockableFiles(LockableFiles):
1970
743
"""A 'LockableFiles' implementation that talks to a smart server.
1972
745
This is not a public interface class.
1985
758
self._dir_mode = None
1986
759
self._file_mode = None
762
"""'get' a remote path as per the LockableFiles interface.
764
:param path: the file to 'get'. If this is 'branch.conf', we do not
765
just retrieve a file, instead we ask the smart server to generate
766
a configuration for us - which is retrieved as an INI file.
768
if path == 'branch.conf':
769
path = self.bzrdir._path_for_remote_call(self._client)
770
response = self._client.call_expecting_body(
771
'Branch.get_config_file', path)
772
assert response[0][0] == 'ok', \
773
'unexpected response code %s' % (response[0],)
774
return StringIO(response[1].read_body_bytes())
777
return LockableFiles.get(self, path)
1989
780
class RemoteBranchFormat(branch.BranchFormat):
1991
def __init__(self, network_name=None):
1992
super(RemoteBranchFormat, self).__init__()
1993
self._matchingbzrdir = RemoteBzrDirFormat()
1994
self._matchingbzrdir.set_branch_format(self)
1995
self._custom_format = None
1996
self._network_name = network_name
1998
782
def __eq__(self, other):
1999
return (isinstance(other, RemoteBranchFormat) and
783
return (isinstance(other, RemoteBranchFormat) and
2000
784
self.__dict__ == other.__dict__)
2002
def _ensure_real(self):
2003
if self._custom_format is None:
2004
self._custom_format = branch.network_format_registry.get(
2007
786
def get_format_description(self):
2009
return 'Remote: ' + self._custom_format.get_format_description()
2011
def network_name(self):
2012
return self._network_name
2014
def open(self, a_bzrdir, ignore_fallbacks=False):
2015
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2017
def _vfs_initialize(self, a_bzrdir):
2018
# Initialisation when using a local bzrdir object, or a non-vfs init
2019
# method is not available on the server.
2020
# self._custom_format is always set - the start of initialize ensures
2022
if isinstance(a_bzrdir, RemoteBzrDir):
2023
a_bzrdir._ensure_real()
2024
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2026
# We assume the bzrdir is parameterised; it may not be.
2027
result = self._custom_format.initialize(a_bzrdir)
2028
if (isinstance(a_bzrdir, RemoteBzrDir) and
2029
not isinstance(result, RemoteBranch)):
2030
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
787
return 'Remote BZR Branch'
789
def get_format_string(self):
790
return 'Remote BZR Branch'
792
def open(self, a_bzrdir):
793
assert isinstance(a_bzrdir, RemoteBzrDir)
794
return a_bzrdir.open_branch()
2033
796
def initialize(self, a_bzrdir):
2034
# 1) get the network name to use.
2035
if self._custom_format:
2036
network_name = self._custom_format.network_name()
2038
# Select the current bzrlib default and ask for that.
2039
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2040
reference_format = reference_bzrdir_format.get_branch_format()
2041
self._custom_format = reference_format
2042
network_name = reference_format.network_name()
2043
# Being asked to create on a non RemoteBzrDir:
2044
if not isinstance(a_bzrdir, RemoteBzrDir):
2045
return self._vfs_initialize(a_bzrdir)
2046
medium = a_bzrdir._client._medium
2047
if medium._is_remote_before((1, 13)):
2048
return self._vfs_initialize(a_bzrdir)
2049
# Creating on a remote bzr dir.
2050
# 2) try direct creation via RPC
2051
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2052
verb = 'BzrDir.create_branch'
2054
response = a_bzrdir._call(verb, path, network_name)
2055
except errors.UnknownSmartMethod:
2056
# Fallback - use vfs methods
2057
medium._remember_remote_is_before((1, 13))
2058
return self._vfs_initialize(a_bzrdir)
2059
if response[0] != 'ok':
2060
raise errors.UnexpectedSmartServerResponse(response)
2061
# Turn the response into a RemoteRepository object.
2062
format = RemoteBranchFormat(network_name=response[1])
2063
repo_format = response_tuple_to_repo_format(response[3:])
2064
if response[2] == '':
2065
repo_bzrdir = a_bzrdir
2067
repo_bzrdir = RemoteBzrDir(
2068
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2070
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2071
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2072
format=format, setup_stacking=False)
2073
# XXX: We know this is a new branch, so it must have revno 0, revid
2074
# NULL_REVISION. Creating the branch locked would make this be unable
2075
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2076
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2077
return remote_branch
2079
def make_tags(self, branch):
2081
return self._custom_format.make_tags(branch)
2083
def supports_tags(self):
2084
# Remote branches might support tags, but we won't know until we
2085
# access the real remote branch.
2087
return self._custom_format.supports_tags()
2089
def supports_stacking(self):
2091
return self._custom_format.supports_stacking()
2093
def supports_set_append_revisions_only(self):
2095
return self._custom_format.supports_set_append_revisions_only()
2098
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
797
assert isinstance(a_bzrdir, RemoteBzrDir)
798
return a_bzrdir.create_branch()
801
class RemoteBranch(branch.Branch):
2099
802
"""Branch stored on a server accessed by HPSS RPC.
2101
804
At the moment most operations are mapped down to simple file operations.
2104
807
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2105
_client=None, format=None, setup_stacking=True):
2106
809
"""Create a RemoteBranch instance.
2108
811
:param real_branch: An optional local implementation of the branch
2109
812
format, usually accessing the data via the VFS.
2110
813
:param _client: Private parameter for testing.
2111
:param format: A RemoteBranchFormat object, None to create one
2112
automatically. If supplied it should have a network_name already
2114
:param setup_stacking: If True make an RPC call to determine the
2115
stacked (or not) status of the branch. If False assume the branch
2118
815
# We intentionally don't call the parent class's __init__, because it
2119
816
# will try to assign to self.tags, which is a property in this subclass.
2120
817
# And the parent's __init__ doesn't do much anyway.
818
self._revision_history_cache = None
2121
819
self.bzrdir = remote_bzrdir
2122
820
if _client is not None:
2123
821
self._client = _client
2125
self._client = remote_bzrdir._client
823
self._client = client._SmartClient(self.bzrdir._shared_medium)
2126
824
self.repository = remote_repository
2127
825
if real_branch is not None:
2128
826
self._real_branch = real_branch
2204
854
Used before calls to self._real_branch.
2206
if self._real_branch is None:
2207
if not vfs.vfs_enabled():
2208
raise AssertionError('smart server vfs must be enabled '
2209
'to use vfs implementation')
856
if not self._real_branch:
857
assert vfs.vfs_enabled()
2210
858
self.bzrdir._ensure_real()
2211
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2212
ignore_fallbacks=self._real_ignore_fallbacks)
2213
if self.repository._real_repository is None:
2214
# Give the remote repository the matching real repo.
2215
real_repo = self._real_branch.repository
2216
if isinstance(real_repo, RemoteRepository):
2217
real_repo._ensure_real()
2218
real_repo = real_repo._real_repository
2219
self.repository._set_real_repository(real_repo)
2220
# Give the real branch the remote repository to let fast-pathing
859
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
860
# Give the remote repository the matching real repo.
861
real_repo = self._real_branch.repository
862
if isinstance(real_repo, RemoteRepository):
863
real_repo._ensure_real()
864
real_repo = real_repo._real_repository
865
self.repository._set_real_repository(real_repo)
866
# Give the branch the remote repository to let fast-pathing happen.
2222
867
self._real_branch.repository = self.repository
868
# XXX: deal with _lock_mode == 'w'
2223
869
if self._lock_mode == 'r':
2224
870
self._real_branch.lock_read()
2225
elif self._lock_mode == 'w':
2226
self._real_branch.lock_write(token=self._lock_token)
2228
def _translate_error(self, err, **context):
2229
self.repository._translate_error(err, branch=self, **context)
2231
def _clear_cached_state(self):
2232
super(RemoteBranch, self)._clear_cached_state()
2233
if self._real_branch is not None:
2234
self._real_branch._clear_cached_state()
2236
def _clear_cached_state_of_remote_branch_only(self):
2237
"""Like _clear_cached_state, but doesn't clear the cache of
2240
This is useful when falling back to calling a method of
2241
self._real_branch that changes state. In that case the underlying
2242
branch changes, so we need to invalidate this RemoteBranch's cache of
2243
it. However, there's no need to invalidate the _real_branch's cache
2244
too, in fact doing so might harm performance.
2246
super(RemoteBranch, self)._clear_cached_state()
2249
873
def control_files(self):
2264
888
self._ensure_real()
2265
889
return self._real_branch.get_physical_lock_status()
2267
def get_stacked_on_url(self):
2268
"""Get the URL this branch is stacked against.
2270
:raises NotStacked: If the branch is not stacked.
2271
:raises UnstackableBranchFormat: If the branch does not support
2273
:raises UnstackableRepositoryFormat: If the repository does not support
2277
# there may not be a repository yet, so we can't use
2278
# self._translate_error, so we can't use self._call either.
2279
response = self._client.call('Branch.get_stacked_on_url',
2280
self._remote_path())
2281
except errors.ErrorFromSmartServer, err:
2282
# there may not be a repository yet, so we can't call through
2283
# its _translate_error
2284
_translate_error(err, branch=self)
2285
except errors.UnknownSmartMethod, err:
2287
return self._real_branch.get_stacked_on_url()
2288
if response[0] != 'ok':
2289
raise errors.UnexpectedSmartServerResponse(response)
2292
def set_stacked_on_url(self, url):
2293
branch.Branch.set_stacked_on_url(self, url)
2295
self._is_stacked = False
2297
self._is_stacked = True
2299
def _vfs_get_tags_bytes(self):
2301
return self._real_branch._get_tags_bytes()
2303
def _get_tags_bytes(self):
2304
medium = self._client._medium
2305
if medium._is_remote_before((1, 13)):
2306
return self._vfs_get_tags_bytes()
2308
response = self._call('Branch.get_tags_bytes', self._remote_path())
2309
except errors.UnknownSmartMethod:
2310
medium._remember_remote_is_before((1, 13))
2311
return self._vfs_get_tags_bytes()
2314
def _vfs_set_tags_bytes(self, bytes):
2316
return self._real_branch._set_tags_bytes(bytes)
2318
def _set_tags_bytes(self, bytes):
2319
medium = self._client._medium
2320
if medium._is_remote_before((1, 18)):
2321
self._vfs_set_tags_bytes(bytes)
2325
self._remote_path(), self._lock_token, self._repo_lock_token)
2326
response = self._call_with_body_bytes(
2327
'Branch.set_tags_bytes', args, bytes)
2328
except errors.UnknownSmartMethod:
2329
medium._remember_remote_is_before((1, 18))
2330
self._vfs_set_tags_bytes(bytes)
2332
891
def lock_read(self):
2333
self.repository.lock_read()
2334
892
if not self._lock_mode:
2335
self._note_lock('r')
2336
893
self._lock_mode = 'r'
2337
894
self._lock_count = 1
2338
895
if self._real_branch is not None:
2347
904
branch_token = token
2348
905
repo_token = self.repository.lock_write()
2349
906
self.repository.unlock()
2350
err_context = {'token': token}
2351
response = self._call(
2352
'Branch.lock_write', self._remote_path(), branch_token,
2353
repo_token or '', **err_context)
2354
if response[0] != 'ok':
907
path = self.bzrdir._path_for_remote_call(self._client)
908
response = self._client.call('Branch.lock_write', path, branch_token,
910
if response[0] == 'ok':
911
ok, branch_token, repo_token = response
912
return branch_token, repo_token
913
elif response[0] == 'LockContention':
914
raise errors.LockContention('(remote lock)')
915
elif response[0] == 'TokenMismatch':
916
raise errors.TokenMismatch(token, '(remote token)')
917
elif response[0] == 'UnlockableTransport':
918
raise errors.UnlockableTransport(self.bzrdir.root_transport)
919
elif response[0] == 'ReadOnlyError':
920
raise errors.ReadOnlyError(self)
2355
922
raise errors.UnexpectedSmartServerResponse(response)
2356
ok, branch_token, repo_token = response
2357
return branch_token, repo_token
2359
924
def lock_write(self, token=None):
2360
925
if not self._lock_mode:
2361
self._note_lock('w')
2362
# Lock the branch and repo in one remote call.
2363
926
remote_tokens = self._remote_lock_write(token)
2364
927
self._lock_token, self._repo_lock_token = remote_tokens
2365
if not self._lock_token:
2366
raise SmartProtocolError('Remote server did not return a token!')
2367
# Tell the self.repository object that it is locked.
2368
self.repository.lock_write(
2369
self._repo_lock_token, _skip_rpc=True)
928
assert self._lock_token, 'Remote server did not return a token!'
929
# TODO: We really, really, really don't want to call _ensure_real
930
# here, but it's the easiest way to ensure coherency between the
931
# state of the RemoteBranch and RemoteRepository objects and the
932
# physical locks. If we don't materialise the real objects here,
933
# then getting everything in the right state later is complex, so
934
# for now we just do it the lazy way.
935
# -- Andrew Bennetts, 2007-02-22.
2371
937
if self._real_branch is not None:
2372
self._real_branch.lock_write(token=self._lock_token)
938
self._real_branch.repository.lock_write(
939
token=self._repo_lock_token)
941
self._real_branch.lock_write(token=self._lock_token)
943
self._real_branch.repository.unlock()
2373
944
if token is not None:
2374
945
self._leave_lock = True
947
# XXX: this case seems to be unreachable; token cannot be None.
2376
948
self._leave_lock = False
2377
949
self._lock_mode = 'w'
2378
950
self._lock_count = 1
2380
952
raise errors.ReadOnlyTransaction
2382
954
if token is not None:
2383
# A token was given to lock_write, and we're relocking, so
2384
# check that the given token actually matches the one we
955
# A token was given to lock_write, and we're relocking, so check
956
# that the given token actually matches the one we already have.
2386
957
if token != self._lock_token:
2387
958
raise errors.TokenMismatch(token, self._lock_token)
2388
959
self._lock_count += 1
2389
# Re-lock the repository too.
2390
self.repository.lock_write(self._repo_lock_token)
2391
return self._lock_token or None
960
return self._lock_token
2393
962
def _unlock(self, branch_token, repo_token):
2394
err_context = {'token': str((branch_token, repo_token))}
2395
response = self._call(
2396
'Branch.unlock', self._remote_path(), branch_token,
2397
repo_token or '', **err_context)
963
path = self.bzrdir._path_for_remote_call(self._client)
964
response = self._client.call('Branch.unlock', path, branch_token,
2398
966
if response == ('ok',):
2400
raise errors.UnexpectedSmartServerResponse(response)
968
elif response[0] == 'TokenMismatch':
969
raise errors.TokenMismatch(
970
str((branch_token, repo_token)), '(remote tokens)')
972
raise errors.UnexpectedSmartServerResponse(response)
2402
@only_raises(errors.LockNotHeld, errors.LockBroken)
2403
974
def unlock(self):
2405
self._lock_count -= 1
2406
if not self._lock_count:
2407
self._clear_cached_state()
2408
mode = self._lock_mode
2409
self._lock_mode = None
2410
if self._real_branch is not None:
2411
if (not self._leave_lock and mode == 'w' and
2412
self._repo_lock_token):
2413
# If this RemoteBranch will remove the physical lock
2414
# for the repository, make sure the _real_branch
2415
# doesn't do it first. (Because the _real_branch's
2416
# repository is set to be the RemoteRepository.)
2417
self._real_branch.repository.leave_lock_in_place()
2418
self._real_branch.unlock()
2420
# Only write-locked branched need to make a remote method
2421
# call to perform the unlock.
2423
if not self._lock_token:
2424
raise AssertionError('Locked, but no token!')
2425
branch_token = self._lock_token
2426
repo_token = self._repo_lock_token
2427
self._lock_token = None
2428
self._repo_lock_token = None
975
self._lock_count -= 1
976
if not self._lock_count:
977
self._clear_cached_state()
978
mode = self._lock_mode
979
self._lock_mode = None
980
if self._real_branch is not None:
2429
981
if not self._leave_lock:
2430
self._unlock(branch_token, repo_token)
2432
self.repository.unlock()
982
# If this RemoteBranch will remove the physical lock for the
983
# repository, make sure the _real_branch doesn't do it
984
# first. (Because the _real_branch's repository is set to
985
# be the RemoteRepository.)
986
self._real_branch.repository.leave_lock_in_place()
987
self._real_branch.unlock()
989
# Only write-locked branched need to make a remote method call
990
# to perfom the unlock.
992
assert self._lock_token, 'Locked, but no token!'
993
branch_token = self._lock_token
994
repo_token = self._repo_lock_token
995
self._lock_token = None
996
self._repo_lock_token = None
997
if not self._leave_lock:
998
self._unlock(branch_token, repo_token)
2434
1000
def break_lock(self):
2435
1001
self._ensure_real()
2436
1002
return self._real_branch.break_lock()
2438
1004
def leave_lock_in_place(self):
2439
if not self._lock_token:
2440
raise NotImplementedError(self.leave_lock_in_place)
2441
1005
self._leave_lock = True
2443
1007
def dont_leave_lock_in_place(self):
2444
if not self._lock_token:
2445
raise NotImplementedError(self.dont_leave_lock_in_place)
2446
1008
self._leave_lock = False
2449
def get_rev_id(self, revno, history=None):
2451
return _mod_revision.NULL_REVISION
2452
last_revision_info = self.last_revision_info()
2453
ok, result = self.repository.get_rev_id_for_revno(
2454
revno, last_revision_info)
2457
missing_parent = result[1]
2458
# Either the revision named by the server is missing, or its parent
2459
# is. Call get_parent_map to determine which, so that we report a
2461
parent_map = self.repository.get_parent_map([missing_parent])
2462
if missing_parent in parent_map:
2463
missing_parent = parent_map[missing_parent]
2464
raise errors.RevisionNotPresent(missing_parent, self.repository)
2466
def _last_revision_info(self):
2467
response = self._call('Branch.last_revision_info', self._remote_path())
2468
if response[0] != 'ok':
2469
raise SmartProtocolError('unexpected response code %s' % (response,))
1010
def last_revision_info(self):
1011
"""See Branch.last_revision_info()."""
1012
path = self.bzrdir._path_for_remote_call(self._client)
1013
response = self._client.call('Branch.last_revision_info', path)
1014
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
2470
1015
revno = int(response[1])
2471
1016
last_revision = response[2]
2472
1017
return (revno, last_revision)
2474
1019
def _gen_revision_history(self):
2475
1020
"""See Branch._gen_revision_history()."""
2476
if self._is_stacked:
2478
return self._real_branch._gen_revision_history()
2479
response_tuple, response_handler = self._call_expecting_body(
2480
'Branch.revision_history', self._remote_path())
2481
if response_tuple[0] != 'ok':
2482
raise errors.UnexpectedSmartServerResponse(response_tuple)
2483
result = response_handler.read_body_bytes().split('\x00')
1021
path = self.bzrdir._path_for_remote_call(self._client)
1022
response = self._client.call_expecting_body(
1023
'Branch.revision_history', path)
1024
assert response[0][0] == 'ok', ('unexpected response code %s'
1026
result = response[1].read_body_bytes().split('\x00')
2484
1027
if result == ['']:
2488
def _remote_path(self):
2489
return self.bzrdir._path_for_remote_call(self._client)
2491
def _set_last_revision_descendant(self, revision_id, other_branch,
2492
allow_diverged=False, allow_overwrite_descendant=False):
2493
# This performs additional work to meet the hook contract; while its
2494
# undesirable, we have to synthesise the revno to call the hook, and
2495
# not calling the hook is worse as it means changes can't be prevented.
2496
# Having calculated this though, we can't just call into
2497
# set_last_revision_info as a simple call, because there is a set_rh
2498
# hook that some folk may still be using.
2499
old_revno, old_revid = self.last_revision_info()
2500
history = self._lefthand_history(revision_id)
2501
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2502
err_context = {'other_branch': other_branch}
2503
response = self._call('Branch.set_last_revision_ex',
2504
self._remote_path(), self._lock_token, self._repo_lock_token,
2505
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2507
self._clear_cached_state()
2508
if len(response) != 3 and response[0] != 'ok':
2509
raise errors.UnexpectedSmartServerResponse(response)
2510
new_revno, new_revision_id = response[1:]
2511
self._last_revision_info_cache = new_revno, new_revision_id
2512
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2513
if self._real_branch is not None:
2514
cache = new_revno, new_revision_id
2515
self._real_branch._last_revision_info_cache = cache
2517
def _set_last_revision(self, revision_id):
2518
old_revno, old_revid = self.last_revision_info()
2519
# This performs additional work to meet the hook contract; while its
2520
# undesirable, we have to synthesise the revno to call the hook, and
2521
# not calling the hook is worse as it means changes can't be prevented.
2522
# Having calculated this though, we can't just call into
2523
# set_last_revision_info as a simple call, because there is a set_rh
2524
# hook that some folk may still be using.
2525
history = self._lefthand_history(revision_id)
2526
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2527
self._clear_cached_state()
2528
response = self._call('Branch.set_last_revision',
2529
self._remote_path(), self._lock_token, self._repo_lock_token,
2531
if response != ('ok',):
2532
raise errors.UnexpectedSmartServerResponse(response)
2533
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2535
1031
@needs_write_lock
2536
1032
def set_revision_history(self, rev_history):
2537
1033
# Send just the tip revision of the history; the server will generate
2538
1034
# the full history from that. If the revision doesn't exist in this
2539
1035
# branch, NoSuchRevision will be raised.
1036
path = self.bzrdir._path_for_remote_call(self._client)
2540
1037
if rev_history == []:
2541
1038
rev_id = 'null:'
2543
1040
rev_id = rev_history[-1]
2544
self._set_last_revision(rev_id)
2545
for hook in branch.Branch.hooks['set_rh']:
2546
hook(self, rev_history)
1041
self._clear_cached_state()
1042
response = self._client.call('Branch.set_last_revision',
1043
path, self._lock_token, self._repo_lock_token, rev_id)
1044
if response[0] == 'NoSuchRevision':
1045
raise NoSuchRevision(self, rev_id)
1047
assert response == ('ok',), (
1048
'unexpected response code %r' % (response,))
2547
1049
self._cache_revision_history(rev_history)
2549
def _get_parent_location(self):
2550
medium = self._client._medium
2551
if medium._is_remote_before((1, 13)):
2552
return self._vfs_get_parent_location()
2554
response = self._call('Branch.get_parent', self._remote_path())
2555
except errors.UnknownSmartMethod:
2556
medium._remember_remote_is_before((1, 13))
2557
return self._vfs_get_parent_location()
2558
if len(response) != 1:
2559
raise errors.UnexpectedSmartServerResponse(response)
2560
parent_location = response[0]
2561
if parent_location == '':
2563
return parent_location
2565
def _vfs_get_parent_location(self):
2567
return self._real_branch._get_parent_location()
2569
def _set_parent_location(self, url):
2570
medium = self._client._medium
2571
if medium._is_remote_before((1, 15)):
2572
return self._vfs_set_parent_location(url)
2574
call_url = url or ''
2575
if type(call_url) is not str:
2576
raise AssertionError('url must be a str or None (%s)' % url)
2577
response = self._call('Branch.set_parent_location',
2578
self._remote_path(), self._lock_token, self._repo_lock_token,
2580
except errors.UnknownSmartMethod:
2581
medium._remember_remote_is_before((1, 15))
2582
return self._vfs_set_parent_location(url)
2584
raise errors.UnexpectedSmartServerResponse(response)
2586
def _vfs_set_parent_location(self, url):
2588
return self._real_branch._set_parent_location(url)
1051
def get_parent(self):
1053
return self._real_branch.get_parent()
1055
def set_parent(self, url):
1057
return self._real_branch.set_parent(url)
1059
def get_config(self):
1060
return RemoteBranchConfig(self)
1062
def sprout(self, to_bzrdir, revision_id=None):
1063
# Like Branch.sprout, except that it sprouts a branch in the default
1064
# format, because RemoteBranches can't be created at arbitrary URLs.
1065
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1066
# to_bzrdir.create_branch...
1067
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1068
self.copy_content_into(result, revision_id=revision_id)
1069
result.set_parent(self.bzrdir.root_transport.base)
1073
def append_revision(self, *revision_ids):
1075
return self._real_branch.append_revision(*revision_ids)
2590
1077
@needs_write_lock
2591
1078
def pull(self, source, overwrite=False, stop_revision=None,
2593
self._clear_cached_state_of_remote_branch_only()
1080
# FIXME: This asks the real branch to run the hooks, which means
1081
# they're called with the wrong target branch parameter.
1082
# The test suite specifically allows this at present but it should be
1083
# fixed. It should get a _override_hook_target branch,
1084
# as push does. -- mbp 20070405
2594
1085
self._ensure_real()
2595
return self._real_branch.pull(
1086
self._real_branch.pull(
2596
1087
source, overwrite=overwrite, stop_revision=stop_revision,
2597
_override_hook_target=self, **kwargs)
2599
1090
@needs_read_lock
2600
1091
def push(self, target, overwrite=False, stop_revision=None):
2606
1097
def is_locked(self):
2607
1098
return self._lock_count >= 1
2610
def revision_id_to_revno(self, revision_id):
2612
return self._real_branch.revision_id_to_revno(revision_id)
2615
1100
def set_last_revision_info(self, revno, revision_id):
2616
# XXX: These should be returned by the set_last_revision_info verb
2617
old_revno, old_revid = self.last_revision_info()
2618
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2619
revision_id = ensure_null(revision_id)
2621
response = self._call('Branch.set_last_revision_info',
2622
self._remote_path(), self._lock_token, self._repo_lock_token,
2623
str(revno), revision_id)
2624
except errors.UnknownSmartMethod:
2626
self._clear_cached_state_of_remote_branch_only()
2627
self._real_branch.set_last_revision_info(revno, revision_id)
2628
self._last_revision_info_cache = revno, revision_id
2630
if response == ('ok',):
2631
self._clear_cached_state()
2632
self._last_revision_info_cache = revno, revision_id
2633
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2634
# Update the _real_branch's cache too.
2635
if self._real_branch is not None:
2636
cache = self._last_revision_info_cache
2637
self._real_branch._last_revision_info_cache = cache
2639
raise errors.UnexpectedSmartServerResponse(response)
1102
self._clear_cached_state()
1103
return self._real_branch.set_last_revision_info(revno, revision_id)
2642
1105
def generate_revision_history(self, revision_id, last_rev=None,
2643
1106
other_branch=None):
2644
medium = self._client._medium
2645
if not medium._is_remote_before((1, 6)):
2646
# Use a smart method for 1.6 and above servers
2648
self._set_last_revision_descendant(revision_id, other_branch,
2649
allow_diverged=True, allow_overwrite_descendant=True)
2651
except errors.UnknownSmartMethod:
2652
medium._remember_remote_is_before((1, 6))
2653
self._clear_cached_state_of_remote_branch_only()
2654
self.set_revision_history(self._lefthand_history(revision_id,
2655
last_rev=last_rev,other_branch=other_branch))
1108
return self._real_branch.generate_revision_history(
1109
revision_id, last_rev=last_rev, other_branch=other_branch)
1114
return self._real_branch.tags
2657
1116
def set_push_location(self, location):
2658
1117
self._ensure_real()
2659
1118
return self._real_branch.set_push_location(location)
2662
class RemoteConfig(object):
2663
"""A Config that reads and writes from smart verbs.
2665
It is a low-level object that considers config data to be name/value pairs
2666
that may be associated with a section. Assigning meaning to the these
2667
values is done at higher levels like bzrlib.config.TreeConfig.
2670
def get_option(self, name, section=None, default=None):
2671
"""Return the value associated with a named option.
2673
:param name: The name of the value
2674
:param section: The section the option is in (if any)
2675
:param default: The value to return if the value is not set
2676
:return: The value or default value
2679
configobj = self._get_configobj()
2681
section_obj = configobj
2684
section_obj = configobj[section]
2687
return section_obj.get(name, default)
2688
except errors.UnknownSmartMethod:
2689
return self._vfs_get_option(name, section, default)
2691
def _response_to_configobj(self, response):
2692
if len(response[0]) and response[0][0] != 'ok':
2693
raise errors.UnexpectedSmartServerResponse(response)
2694
lines = response[1].read_body_bytes().splitlines()
2695
return config.ConfigObj(lines, encoding='utf-8')
2698
class RemoteBranchConfig(RemoteConfig):
2699
"""A RemoteConfig for Branches."""
2701
def __init__(self, branch):
2702
self._branch = branch
2704
def _get_configobj(self):
2705
path = self._branch._remote_path()
2706
response = self._branch._client.call_expecting_body(
2707
'Branch.get_config_file', path)
2708
return self._response_to_configobj(response)
2710
def set_option(self, value, name, section=None):
2711
"""Set the value associated with a named option.
2713
:param value: The value to set
2714
:param name: The name of the value to set
2715
:param section: The section the option is in (if any)
2717
medium = self._branch._client._medium
2718
if medium._is_remote_before((1, 14)):
2719
return self._vfs_set_option(value, name, section)
2721
path = self._branch._remote_path()
2722
response = self._branch._client.call('Branch.set_config_option',
2723
path, self._branch._lock_token, self._branch._repo_lock_token,
2724
value.encode('utf8'), name, section or '')
2725
except errors.UnknownSmartMethod:
2726
medium._remember_remote_is_before((1, 14))
2727
return self._vfs_set_option(value, name, section)
2729
raise errors.UnexpectedSmartServerResponse(response)
2731
def _real_object(self):
2732
self._branch._ensure_real()
2733
return self._branch._real_branch
2735
def _vfs_set_option(self, value, name, section=None):
2736
return self._real_object()._get_config().set_option(
2737
value, name, section)
2740
class RemoteBzrDirConfig(RemoteConfig):
2741
"""A RemoteConfig for BzrDirs."""
2743
def __init__(self, bzrdir):
2744
self._bzrdir = bzrdir
2746
def _get_configobj(self):
2747
medium = self._bzrdir._client._medium
2748
verb = 'BzrDir.get_config_file'
2749
if medium._is_remote_before((1, 15)):
2750
raise errors.UnknownSmartMethod(verb)
2751
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2752
response = self._bzrdir._call_expecting_body(
2754
return self._response_to_configobj(response)
2756
def _vfs_get_option(self, name, section, default):
2757
return self._real_object()._get_config().get_option(
2758
name, section, default)
2760
def set_option(self, value, name, section=None):
2761
"""Set the value associated with a named option.
2763
:param value: The value to set
2764
:param name: The name of the value to set
2765
:param section: The section the option is in (if any)
2767
return self._real_object()._get_config().set_option(
2768
value, name, section)
2770
def _real_object(self):
2771
self._bzrdir._ensure_real()
2772
return self._bzrdir._real_bzrdir
1120
def update_revisions(self, other, stop_revision=None):
1122
return self._real_branch.update_revisions(
1123
other, stop_revision=stop_revision)
1126
class RemoteBranchConfig(BranchConfig):
1129
self.branch._ensure_real()
1130
return self.branch._real_branch.get_config().username()
1132
def _get_branch_data_config(self):
1133
self.branch._ensure_real()
1134
if self._branch_data_config is None:
1135
self._branch_data_config = TreeConfig(self.branch._real_branch)
1136
return self._branch_data_config
2776
1139
def _extract_tar(tar, to_dir):
2781
1144
for tarinfo in tar:
2782
1145
tar.extract(tarinfo, to_dir)
2785
def _translate_error(err, **context):
2786
"""Translate an ErrorFromSmartServer into a more useful error.
2788
Possible context keys:
2796
If the error from the server doesn't match a known pattern, then
2797
UnknownErrorFromSmartServer is raised.
2801
return context[name]
2802
except KeyError, key_err:
2803
mutter('Missing key %r in context %r', key_err.args[0], context)
2806
"""Get the path from the context if present, otherwise use first error
2810
return context['path']
2811
except KeyError, key_err:
2813
return err.error_args[0]
2814
except IndexError, idx_err:
2816
'Missing key %r in context %r', key_err.args[0], context)
2819
if err.error_verb == 'IncompatibleRepositories':
2820
raise errors.IncompatibleRepositories(err.error_args[0],
2821
err.error_args[1], err.error_args[2])
2822
elif err.error_verb == 'NoSuchRevision':
2823
raise NoSuchRevision(find('branch'), err.error_args[0])
2824
elif err.error_verb == 'nosuchrevision':
2825
raise NoSuchRevision(find('repository'), err.error_args[0])
2826
elif err.error_tuple == ('nobranch',):
2827
raise errors.NotBranchError(path=find('bzrdir').root_transport.base)
2828
elif err.error_verb == 'norepository':
2829
raise errors.NoRepositoryPresent(find('bzrdir'))
2830
elif err.error_verb == 'LockContention':
2831
raise errors.LockContention('(remote lock)')
2832
elif err.error_verb == 'UnlockableTransport':
2833
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2834
elif err.error_verb == 'LockFailed':
2835
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2836
elif err.error_verb == 'TokenMismatch':
2837
raise errors.TokenMismatch(find('token'), '(remote token)')
2838
elif err.error_verb == 'Diverged':
2839
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2840
elif err.error_verb == 'TipChangeRejected':
2841
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2842
elif err.error_verb == 'UnstackableBranchFormat':
2843
raise errors.UnstackableBranchFormat(*err.error_args)
2844
elif err.error_verb == 'UnstackableRepositoryFormat':
2845
raise errors.UnstackableRepositoryFormat(*err.error_args)
2846
elif err.error_verb == 'NotStacked':
2847
raise errors.NotStacked(branch=find('branch'))
2848
elif err.error_verb == 'PermissionDenied':
2850
if len(err.error_args) >= 2:
2851
extra = err.error_args[1]
2854
raise errors.PermissionDenied(path, extra=extra)
2855
elif err.error_verb == 'ReadError':
2857
raise errors.ReadError(path)
2858
elif err.error_verb == 'NoSuchFile':
2860
raise errors.NoSuchFile(path)
2861
elif err.error_verb == 'FileExists':
2862
raise errors.FileExists(err.error_args[0])
2863
elif err.error_verb == 'DirectoryNotEmpty':
2864
raise errors.DirectoryNotEmpty(err.error_args[0])
2865
elif err.error_verb == 'ShortReadvError':
2866
args = err.error_args
2867
raise errors.ShortReadvError(
2868
args[0], int(args[1]), int(args[2]), int(args[3]))
2869
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2870
encoding = str(err.error_args[0]) # encoding must always be a string
2871
val = err.error_args[1]
2872
start = int(err.error_args[2])
2873
end = int(err.error_args[3])
2874
reason = str(err.error_args[4]) # reason must always be a string
2875
if val.startswith('u:'):
2876
val = val[2:].decode('utf-8')
2877
elif val.startswith('s:'):
2878
val = val[2:].decode('base64')
2879
if err.error_verb == 'UnicodeDecodeError':
2880
raise UnicodeDecodeError(encoding, val, start, end, reason)
2881
elif err.error_verb == 'UnicodeEncodeError':
2882
raise UnicodeEncodeError(encoding, val, start, end, reason)
2883
elif err.error_verb == 'ReadOnlyError':
2884
raise errors.TransportNotPossible('readonly transport')
2885
raise errors.UnknownErrorFromSmartServer(err)