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.
19
22
from bzrlib import (
30
repository as _mod_repository,
32
revision as _mod_revision,
36
32
from bzrlib.branch import BranchReferenceFormat
37
33
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
34
from bzrlib.decorators import needs_read_lock, needs_write_lock
39
35
from bzrlib.errors import (
41
37
SmartProtocolError,
43
39
from bzrlib.lockable_files import LockableFiles
44
from bzrlib.smart import client, vfs, repository as smart_repo
40
from bzrlib.smart import client, vfs
45
41
from bzrlib.revision import ensure_null, NULL_REVISION
46
42
from bzrlib.trace import mutter, note, warning
49
class _RpcHelper(object):
50
"""Mixin class that helps with issuing RPCs."""
52
def _call(self, method, *args, **err_context):
54
return self._client.call(method, *args)
55
except errors.ErrorFromSmartServer, err:
56
self._translate_error(err, **err_context)
58
def _call_expecting_body(self, method, *args, **err_context):
60
return self._client.call_expecting_body(method, *args)
61
except errors.ErrorFromSmartServer, err:
62
self._translate_error(err, **err_context)
64
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
66
return self._client.call_with_body_bytes(method, args, body_bytes)
67
except errors.ErrorFromSmartServer, err:
68
self._translate_error(err, **err_context)
70
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
73
return self._client.call_with_body_bytes_expecting_body(
74
method, args, body_bytes)
75
except errors.ErrorFromSmartServer, err:
76
self._translate_error(err, **err_context)
79
def response_tuple_to_repo_format(response):
80
"""Convert a response tuple describing a repository format to a format."""
81
format = RemoteRepositoryFormat()
82
format._rich_root_data = (response[0] == 'yes')
83
format._supports_tree_reference = (response[1] == 'yes')
84
format._supports_external_lookups = (response[2] == 'yes')
85
format._network_name = response[3]
89
45
# Note: RemoteBzrDirFormat is in bzrdir.py
91
class RemoteBzrDir(BzrDir, _RpcHelper):
47
class RemoteBzrDir(BzrDir):
92
48
"""Control directory on a remote server, accessed via bzr:// or similar."""
94
def __init__(self, transport, format, _client=None, _force_probe=False):
50
def __init__(self, transport, _client=None):
95
51
"""Construct a RemoteBzrDir.
97
53
:param _client: Private parameter for testing. Disables probing and the
98
54
use of a real bzrdir.
100
BzrDir.__init__(self, transport, format)
56
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
101
57
# this object holds a delegated bzrdir that uses file-level operations
102
58
# to talk to the other side
103
59
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
61
if _client is None:
110
62
medium = transport.get_smart_medium()
111
63
self._client = client._SmartClient(medium)
113
65
self._client = _client
120
return '%s(%r)' % (self.__class__.__name__, self._client)
122
def _probe_bzrdir(self):
123
medium = self._client._medium
124
68
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)
69
response = self._client.call('BzrDir.open', path)
151
70
if response not in [('yes',), ('no',)]:
152
71
raise errors.UnexpectedSmartServerResponse(response)
153
72
if response == ('no',):
154
raise errors.NotBranchError(path=self.root_transport.base)
73
raise errors.NotBranchError(path=transport.base)
156
75
def _ensure_real(self):
157
76
"""Ensure that there is a _real_bzrdir set.
159
78
Used before calls to self._real_bzrdir.
161
80
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
81
self._real_bzrdir = BzrDir.open_from_transport(
167
82
self.root_transport, _server_formats=False)
168
self._format._network_name = \
169
self._real_bzrdir._format.network_name()
84
def cloning_metadir(self, stacked=False):
86
return self._real_bzrdir.cloning_metadir(stacked)
171
88
def _translate_error(self, err, **context):
172
89
_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
91
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()
93
self._real_bzrdir.create_repository(shared=shared)
94
return self.open_repository()
242
96
def destroy_repository(self):
243
97
"""See BzrDir.destroy_repository"""
244
98
self._ensure_real()
245
99
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
101
def create_branch(self):
103
real_branch = self._real_bzrdir.create_branch()
104
return RemoteBranch(self, self.find_repository(), real_branch)
266
def destroy_branch(self, name=None):
106
def destroy_branch(self):
267
107
"""See BzrDir.destroy_branch"""
268
108
self._ensure_real()
269
self._real_bzrdir.destroy_branch(name=name)
270
self._next_open_branch_result = None
109
self._real_bzrdir.destroy_branch()
272
111
def create_workingtree(self, revision_id=None, from_branch=None):
273
112
raise errors.NotLocalUrl(self.transport.base)
283
122
def get_branch_reference(self):
284
123
"""See BzrDir.get_branch_reference()."""
285
response = self._get_branch_reference()
286
if response[0] == 'ref':
124
path = self._path_for_remote_call(self._client)
126
response = self._client.call('BzrDir.open_branch', path)
127
except errors.ErrorFromSmartServer, err:
128
self._translate_error(err)
129
if response[0] == 'ok':
130
if response[1] == '':
131
# branch at this location.
134
# a branch reference, use the existing BranchReference logic.
291
def _get_branch_reference(self):
292
path = self._path_for_remote_call(self._client)
293
medium = self._client._medium
295
('BzrDir.open_branchV3', (2, 1)),
296
('BzrDir.open_branchV2', (1, 13)),
297
('BzrDir.open_branch', None),
299
for verb, required_version in candidate_calls:
300
if required_version and medium._is_remote_before(required_version):
303
response = self._call(verb, path)
304
except errors.UnknownSmartMethod:
305
if required_version is None:
307
medium._remember_remote_is_before(required_version)
310
if verb == 'BzrDir.open_branch':
311
if response[0] != 'ok':
312
raise errors.UnexpectedSmartServerResponse(response)
313
if response[1] != '':
314
return ('ref', response[1])
316
return ('branch', '')
317
if response[0] not in ('ref', 'branch'):
318
137
raise errors.UnexpectedSmartServerResponse(response)
321
139
def _get_tree_branch(self):
322
140
"""See BzrDir._get_tree_branch()."""
323
141
return None, self.open_branch()
325
def open_branch(self, name=None, unsupported=False,
326
ignore_fallbacks=False):
143
def open_branch(self, _unsupported=False):
328
145
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':
146
reference_url = self.get_branch_reference()
147
if reference_url is None:
148
# branch at this location.
149
return RemoteBranch(self, self.find_repository())
336
151
# a branch reference, use the existing BranchReference logic.
337
152
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):
153
return format.open(self, _found=True, location=reference_url)
155
def open_repository(self):
156
path = self._path_for_remote_call(self._client)
360
157
verb = 'BzrDir.find_repositoryV2'
361
response = self._call(verb, path)
362
if response[0] != 'ok':
363
raise errors.UnexpectedSmartServerResponse(response)
365
repo = self._real_bzrdir.open_repository()
366
response = response + (repo._format.network_name(),)
367
return response, repo
369
def _open_repo_v3(self, path):
370
verb = 'BzrDir.find_repositoryV3'
371
medium = self._client._medium
372
if medium._is_remote_before((1, 13)):
373
raise errors.UnknownSmartMethod(verb)
375
response = self._call(verb, path)
376
except errors.UnknownSmartMethod:
377
medium._remember_remote_is_before((1, 13))
379
if response[0] != 'ok':
380
raise errors.UnexpectedSmartServerResponse(response)
381
return response, None
383
def open_repository(self):
384
path = self._path_for_remote_call(self._client)
386
for probe in [self._open_repo_v3, self._open_repo_v2,
389
response, real_repo = probe(path)
160
response = self._client.call(verb, path)
391
161
except errors.UnknownSmartMethod:
394
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
162
verb = 'BzrDir.find_repository'
163
response = self._client.call(verb, path)
164
except errors.ErrorFromSmartServer, err:
165
self._translate_error(err)
395
166
if response[0] != 'ok':
396
167
raise errors.UnexpectedSmartServerResponse(response)
397
if len(response) != 6:
168
if verb == 'BzrDir.find_repository':
169
# servers that don't support the V2 method don't support external
171
response = response + ('no', )
172
if not (len(response) == 5):
398
173
raise SmartProtocolError('incorrect response length %s' % (response,))
399
174
if response[1] == '':
400
# repo is at this dir.
401
format = response_tuple_to_repo_format(response[2:])
175
format = RemoteRepositoryFormat()
176
format.rich_root_data = (response[2] == 'yes')
177
format.supports_tree_reference = (response[3] == 'yes')
178
# No wire format to check this yet.
179
format.supports_external_lookups = (response[4] == 'yes')
402
180
# Used to support creating a real format instance when needed.
403
181
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)
182
return RemoteRepository(self, format)
410
184
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
186
def open_workingtree(self, recommend_upgrade=True):
419
if self.has_workingtree():
188
if self._real_bzrdir.has_workingtree():
420
189
raise errors.NotLocalUrl(self.root_transport)
422
191
raise errors.NoWorkingTree(self.root_transport.base)
469
236
the attributes rich_root_data and supports_tree_reference are set
470
237
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
241
_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
243
def initialize(self, a_bzrdir, shared=False):
244
if not isinstance(a_bzrdir, RemoteBzrDir):
540
245
prior_repo = self._creating_bzrdir.open_repository()
541
246
prior_repo._ensure_real()
542
result = prior_repo._real_repository._format.initialize(
247
return prior_repo._real_repository._format.initialize(
543
248
a_bzrdir, shared=shared)
545
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
546
# support remote initialization.
547
# We delegate to a real object at this point (as RemoteBzrDir
548
# delegate to the repository format which would lead to infinite
549
# recursion if we just called a_bzrdir.create_repository.
550
a_bzrdir._ensure_real()
551
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
552
if not isinstance(result, RemoteRepository):
553
return self.open(a_bzrdir)
557
def initialize(self, a_bzrdir, shared=False):
558
# Being asked to create on a non RemoteBzrDir:
559
if not isinstance(a_bzrdir, RemoteBzrDir):
560
return self._vfs_initialize(a_bzrdir, shared)
561
medium = a_bzrdir._client._medium
562
if medium._is_remote_before((1, 13)):
563
return self._vfs_initialize(a_bzrdir, shared)
564
# Creating on a remote bzr dir.
565
# 1) get the network name to use.
566
if self._custom_format:
567
network_name = self._custom_format.network_name()
568
elif self._network_name:
569
network_name = self._network_name
571
# Select the current bzrlib default and ask for that.
572
reference_bzrdir_format = bzrdir.format_registry.get('default')()
573
reference_format = reference_bzrdir_format.repository_format
574
network_name = reference_format.network_name()
575
# 2) try direct creation via RPC
576
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
577
verb = 'BzrDir.create_repository'
583
response = a_bzrdir._call(verb, path, network_name, shared_str)
584
except errors.UnknownSmartMethod:
585
# Fallback - use vfs methods
586
medium._remember_remote_is_before((1, 13))
587
return self._vfs_initialize(a_bzrdir, shared)
589
# Turn the response into a RemoteRepository object.
590
format = response_tuple_to_repo_format(response[1:])
591
# Used to support creating a real format instance when needed.
592
format._creating_bzrdir = a_bzrdir
593
remote_repo = RemoteRepository(a_bzrdir, format)
594
format._creating_repo = remote_repo
249
return a_bzrdir.create_repository(shared=shared)
597
251
def open(self, a_bzrdir):
598
252
if not isinstance(a_bzrdir, RemoteBzrDir):
599
253
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
600
254
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
256
def get_format_description(self):
624
return 'Remote: ' + self._custom_format.get_format_description()
257
return 'bzr remote repository'
626
259
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):
260
return self.__class__ == other.__class__
262
def check_conversion_target(self, target_format):
263
if self.rich_root_data and not target_format.rich_root_data:
264
raise errors.BadConversionTarget(
265
'Does not support rich root data.', target_format)
266
if (self.supports_tree_reference and
267
not getattr(target_format, 'supports_tree_reference', False)):
268
raise errors.BadConversionTarget(
269
'Does not support nested trees', target_format)
272
class RemoteRepository(object):
648
273
"""Repository accessed over rpc.
650
275
For the moment most operations are performed using local transport-backed
744
344
self._ensure_real()
745
345
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
347
def _ensure_real(self):
792
348
"""Ensure that there is a _real_repository set.
794
350
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
352
if self._real_repository is None:
805
if 'hpssvfs' in debug.debug_flags:
807
warning('VFS Repository access triggered\n%s',
808
''.join(traceback.format_stack()))
809
self._unstacked_provider.missing_keys.clear()
810
353
self.bzrdir._ensure_real()
811
354
self._set_real_repository(
812
355
self.bzrdir._real_bzrdir.open_repository())
861
412
for line in lines:
862
413
d = tuple(line.split())
863
414
revision_graph[d[0]] = d[1:]
865
416
return revision_graph
868
"""See Repository._get_sink()."""
869
return RemoteStreamSink(self)
871
def _get_source(self, to_format):
872
"""Return a source for streaming from this repository."""
873
return RemoteStreamSource(self, to_format)
876
418
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,))
419
"""See Repository.has_revision()."""
420
if revision_id == NULL_REVISION:
421
# The null revision is always present.
423
path = self.bzrdir._path_for_remote_call(self._client)
424
response = self._client.call(
425
'Repository.has_revision', path, revision_id)
426
if response[0] not in ('yes', 'no'):
427
raise errors.UnexpectedSmartServerResponse(response)
428
return response[0] == 'yes'
882
430
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)
431
"""See Repository.has_revisions()."""
433
for revision_id in revision_ids:
434
if self.has_revision(revision_id):
435
result.add(revision_id)
895
def _has_same_fallbacks(self, other_repo):
896
"""Returns true if the repositories have the same fallbacks."""
897
# XXX: copied from Repository; it should be unified into a base class
898
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
899
my_fb = self._fallback_repositories
900
other_fb = other_repo._fallback_repositories
901
if len(my_fb) != len(other_fb):
903
for f, g in zip(my_fb, other_fb):
904
if not f.has_same_location(g):
908
438
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
439
return (self.__class__ == other.__class__ and
913
440
self.bzrdir.transport.base == other.bzrdir.transport.base)
915
442
def get_graph(self, other_repository=None):
916
443
"""Return the graph for this repository format"""
917
parents_provider = self._make_parents_provider(other_repository)
444
parents_provider = self
445
if (other_repository is not None and
446
other_repository.bzrdir.transport.base !=
447
self.bzrdir.transport.base):
448
parents_provider = graph._StackedParentsProvider(
449
[parents_provider, other_repository._make_parents_provider()])
918
450
return graph.Graph(parents_provider)
921
def get_known_graph_ancestry(self, revision_ids):
922
"""Return the known graph for a set of revision ids and their ancestors.
924
st = static_tuple.StaticTuple
925
revision_keys = [st(r_id).intern() for r_id in revision_ids]
926
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
927
return graph.GraphThunkIdsToKeys(known_graph)
929
452
def gather_stats(self, revid=None, committers=None):
930
453
"""See Repository.gather_stats()."""
931
454
path = self.bzrdir._path_for_remote_call(self._client)
1227
713
def add_fallback_repository(self, repository):
1228
714
"""Add a repository to use for looking up data not held locally.
1230
716
:param repository: A repository.
1232
718
if not self._format.supports_external_lookups:
1233
raise errors.UnstackableRepositoryFormat(
1234
self._format.network_name(), self.base)
719
raise errors.UnstackableRepositoryFormat(self._format, self.base)
1235
720
# We need to accumulate additional repositories here, to pass them in
1236
721
# on various RPC's.
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
722
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
724
def add_inventory(self, revid, inv, parents):
1265
725
self._ensure_real()
1266
726
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
728
def add_revision(self, rev_id, rev, inv=None, config=None):
1276
729
self._ensure_real()
1277
730
return self._real_repository.add_revision(
1387
811
self._ensure_real()
1388
812
return self._real_repository._get_versioned_file_checker(
1389
813
revisions, revision_versions_cache)
1391
815
def iter_files_bytes(self, desired_files):
1392
816
"""See Repository.iter_file_bytes.
1394
818
self._ensure_real()
1395
819
return self._real_repository.iter_files_bytes(desired_files)
1397
def get_parent_map(self, revision_ids):
822
def _fetch_order(self):
823
"""Decorate the real repository for now.
825
In the long term getting this back from the remote repository as part
826
of open would be more efficient.
829
return self._real_repository._fetch_order
832
def _fetch_uses_deltas(self):
833
"""Decorate the real repository for now.
835
In the long term getting this back from the remote repository as part
836
of open would be more efficient.
839
return self._real_repository._fetch_uses_deltas
842
def _fetch_reconcile(self):
843
"""Decorate the real repository for now.
845
In the long term getting this back from the remote repository as part
846
of open would be more efficient.
849
return self._real_repository._fetch_reconcile
851
def get_parent_map(self, keys):
1398
852
"""See bzrlib.Graph.get_parent_map()."""
1399
return self._make_parents_provider().get_parent_map(revision_ids)
853
# Hack to build up the caching logic.
854
ancestry = self._parents_map
856
# Repository is not locked, so there's no cache.
857
missing_revisions = set(keys)
860
missing_revisions = set(key for key in keys if key not in ancestry)
861
if missing_revisions:
862
parent_map = self._get_parent_map(missing_revisions)
863
if 'hpss' in debug.debug_flags:
864
mutter('retransmitted revisions: %d of %d',
865
len(set(ancestry).intersection(parent_map)),
867
ancestry.update(parent_map)
868
present_keys = [k for k in keys if k in ancestry]
869
if 'hpss' in debug.debug_flags:
870
if self._requested_parents is not None and len(ancestry) != 0:
871
self._requested_parents.update(present_keys)
872
mutter('Current RemoteRepository graph hit rate: %d%%',
873
100.0 * len(self._requested_parents) / len(ancestry))
874
return dict((k, ancestry[k]) for k in present_keys)
1401
def _get_parent_map_rpc(self, keys):
876
def _get_parent_map(self, keys):
1402
877
"""Helper for get_parent_map that performs the RPC."""
1403
878
medium = self._client._medium
1404
879
if medium._is_remote_before((1, 2)):
1405
880
# We already found out that the server can't understand
1406
881
# 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
883
# XXX: Note that this will issue a deprecation warning. This is ok
884
# :- its because we're working with a deprecated server anyway, and
885
# the user will almost certainly have seen a warning about the
886
# server version already.
887
rg = self.get_revision_graph()
888
# There is an api discrepency between get_parent_map and
1415
889
# get_revision_graph. Specifically, a "key:()" pair in
1416
890
# get_revision_graph just means a node has no parents. For
1417
891
# "get_parent_map" it means the node is a ghost. So fix up the
1742
1182
:param recipe: A search recipe (start, stop, count).
1743
1183
:return: Serialised bytes.
1745
start_keys = ' '.join(recipe[1])
1746
stop_keys = ' '.join(recipe[2])
1747
count = str(recipe[3])
1185
start_keys = ' '.join(recipe[0])
1186
stop_keys = ' '.join(recipe[1])
1187
count = str(recipe[2])
1748
1188
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
1191
class RemoteBranchLockableFiles(LockableFiles):
2018
1192
"""A 'LockableFiles' implementation that talks to a smart server.
2020
1194
This is not a public interface class.
2037
1211
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
1213
def __eq__(self, other):
2047
return (isinstance(other, RemoteBranchFormat) and
1214
return (isinstance(other, RemoteBranchFormat) and
2048
1215
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
1217
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)
1218
return 'Remote BZR Branch'
1220
def get_format_string(self):
1221
return 'Remote BZR Branch'
1223
def open(self, a_bzrdir):
1224
return a_bzrdir.open_branch()
1226
def initialize(self, a_bzrdir):
1227
return a_bzrdir.create_branch()
2137
1229
def supports_tags(self):
2138
1230
# Remote branches might support tags, but we won't know until we
2139
1231
# 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):
1235
class RemoteBranch(branch.Branch):
2153
1236
"""Branch stored on a server accessed by HPSS RPC.
2155
1238
At the moment most operations are mapped down to simple file operations.
2158
1241
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2159
_client=None, format=None, setup_stacking=True, name=None):
2160
1243
"""Create a RemoteBranch instance.
2162
1245
:param real_branch: An optional local implementation of the branch
2163
1246
format, usually accessing the data via the VFS.
2164
1247
: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
1249
# We intentionally don't call the parent class's __init__, because it
2174
1250
# will try to assign to self.tags, which is a property in this subclass.
2175
1251
# And the parent's __init__ doesn't do much anyway.
1252
self._revision_id_to_revno_cache = None
1253
self._revision_history_cache = None
1254
self._last_revision_info_cache = None
2176
1255
self.bzrdir = remote_bzrdir
2177
1256
if _client is not None:
2178
1257
self._client = _client
2330
1366
:raises UnstackableRepositoryFormat: If the repository does not support
2334
# there may not be a repository yet, so we can't use
2335
# self._translate_error, so we can't use self._call either.
2336
response = self._client.call('Branch.get_stacked_on_url',
2337
self._remote_path())
2338
except errors.ErrorFromSmartServer, err:
2339
# there may not be a repository yet, so we can't call through
2340
# its _translate_error
2341
_translate_error(err, branch=self)
2342
except errors.UnknownSmartMethod, err:
2344
return self._real_branch.get_stacked_on_url()
2345
if response[0] != 'ok':
2346
raise errors.UnexpectedSmartServerResponse(response)
2349
def set_stacked_on_url(self, url):
2350
branch.Branch.set_stacked_on_url(self, url)
2352
self._is_stacked = False
2354
self._is_stacked = True
2356
def _vfs_get_tags_bytes(self):
2358
return self._real_branch._get_tags_bytes()
2360
def _get_tags_bytes(self):
2361
medium = self._client._medium
2362
if medium._is_remote_before((1, 13)):
2363
return self._vfs_get_tags_bytes()
2365
response = self._call('Branch.get_tags_bytes', self._remote_path())
2366
except errors.UnknownSmartMethod:
2367
medium._remember_remote_is_before((1, 13))
2368
return self._vfs_get_tags_bytes()
2371
def _vfs_set_tags_bytes(self, bytes):
2373
return self._real_branch._set_tags_bytes(bytes)
2375
def _set_tags_bytes(self, bytes):
2376
medium = self._client._medium
2377
if medium._is_remote_before((1, 18)):
2378
self._vfs_set_tags_bytes(bytes)
2382
self._remote_path(), self._lock_token, self._repo_lock_token)
2383
response = self._call_with_body_bytes(
2384
'Branch.set_tags_bytes', args, bytes)
2385
except errors.UnknownSmartMethod:
2386
medium._remember_remote_is_before((1, 18))
2387
self._vfs_set_tags_bytes(bytes)
1370
return self._real_branch.get_stacked_on_url()
2389
1372
def lock_read(self):
2390
1373
self.repository.lock_read()
2391
1374
if not self._lock_mode:
2392
self._note_lock('r')
2393
1375
self._lock_mode = 'r'
2394
1376
self._lock_count = 1
2395
1377
if self._real_branch is not None:
2545
def _remote_path(self):
2546
return self.bzrdir._path_for_remote_call(self._client)
2548
1510
def _set_last_revision_descendant(self, revision_id, other_branch,
2549
1511
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),
1512
path = self.bzrdir._path_for_remote_call(self._client)
1514
response = self._client.call('Branch.set_last_revision_ex',
1515
path, self._lock_token, self._repo_lock_token, revision_id,
1516
int(allow_diverged), int(allow_overwrite_descendant))
1517
except errors.ErrorFromSmartServer, err:
1518
self._translate_error(err, other_branch=other_branch)
2564
1519
self._clear_cached_state()
2565
1520
if len(response) != 3 and response[0] != 'ok':
2566
1521
raise errors.UnexpectedSmartServerResponse(response)
2567
1522
new_revno, new_revision_id = response[1:]
2568
1523
self._last_revision_info_cache = new_revno, new_revision_id
2569
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2570
1524
if self._real_branch is not None:
2571
1525
cache = new_revno, new_revision_id
2572
1526
self._real_branch._last_revision_info_cache = cache
2574
1528
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)
1529
path = self.bzrdir._path_for_remote_call(self._client)
2584
1530
self._clear_cached_state()
2585
response = self._call('Branch.set_last_revision',
2586
self._remote_path(), self._lock_token, self._repo_lock_token,
1532
response = self._client.call('Branch.set_last_revision',
1533
path, self._lock_token, self._repo_lock_token, revision_id)
1534
except errors.ErrorFromSmartServer, err:
1535
self._translate_error(err)
2588
1536
if response != ('ok',):
2589
1537
raise errors.UnexpectedSmartServerResponse(response)
2590
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2592
1539
@needs_write_lock
2593
1540
def set_revision_history(self, rev_history):
2600
1547
rev_id = rev_history[-1]
2601
1548
self._set_last_revision(rev_id)
2602
for hook in branch.Branch.hooks['set_rh']:
2603
hook(self, rev_history)
2604
1549
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)
1551
def get_parent(self):
1553
return self._real_branch.get_parent()
1555
def set_parent(self, url):
1557
return self._real_branch.set_parent(url)
1559
def set_stacked_on_url(self, stacked_location):
1560
"""Set the URL this branch is stacked against.
1562
:raises UnstackableBranchFormat: If the branch does not support
1564
:raises UnstackableRepositoryFormat: If the repository does not support
1568
return self._real_branch.set_stacked_on_url(stacked_location)
1570
def sprout(self, to_bzrdir, revision_id=None):
1571
# Like Branch.sprout, except that it sprouts a branch in the default
1572
# format, because RemoteBranches can't be created at arbitrary URLs.
1573
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1574
# to_bzrdir.create_branch...
1576
result = self._real_branch._format.initialize(to_bzrdir)
1577
self.copy_content_into(result, revision_id=revision_id)
1578
result.set_parent(self.bzrdir.root_transport.base)
2647
1581
@needs_write_lock
2648
1582
def pull(self, source, overwrite=False, stop_revision=None,
2708
1639
except errors.UnknownSmartMethod:
2709
1640
medium._remember_remote_is_before((1, 6))
2710
1641
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))
1643
self._real_branch.generate_revision_history(
1644
revision_id, last_rev=last_rev, other_branch=other_branch)
1649
return self._real_branch.tags
2714
1651
def set_push_location(self, location):
2715
1652
self._ensure_real()
2716
1653
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
1656
def update_revisions(self, other, stop_revision=None, overwrite=False,
1658
"""See Branch.update_revisions."""
2736
configobj = self._get_configobj()
2738
section_obj = configobj
1661
if stop_revision is None:
1662
stop_revision = other.last_revision()
1663
if revision.is_null(stop_revision):
1664
# if there are no commits, we're done.
1666
self.fetch(other, stop_revision)
1669
# Just unconditionally set the new revision. We don't care if
1670
# the branches have diverged.
1671
self._set_last_revision(stop_revision)
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
1673
medium = self._client._medium
1674
if not medium._is_remote_before((1, 6)):
1676
self._set_last_revision_descendant(stop_revision, other)
1678
except errors.UnknownSmartMethod:
1679
medium._remember_remote_is_before((1, 6))
1680
# Fallback for pre-1.6 servers: check for divergence
1681
# client-side, then do _set_last_revision.
1682
last_rev = revision.ensure_null(self.last_revision())
1684
graph = self.repository.get_graph()
1685
if self._check_if_descendant_or_diverged(
1686
stop_revision, last_rev, graph, other):
1687
# stop_revision is a descendant of last_rev, but we aren't
1688
# overwriting, so we're done.
1690
self._set_last_revision(stop_revision)
2833
1695
def _extract_tar(tar, to_dir):
2901
1740
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2902
1741
elif err.error_verb == 'TipChangeRejected':
2903
1742
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
1743
raise errors.UnknownErrorFromSmartServer(err)