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., 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.
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22
19
from bzrlib import (
30
repository as _mod_repository,
32
revision as _mod_revision,
33
36
from bzrlib.branch import BranchReferenceFormat
34
37
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
35
from bzrlib.decorators import needs_read_lock, needs_write_lock
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
36
39
from bzrlib.errors import (
38
41
SmartProtocolError,
40
43
from bzrlib.lockable_files import LockableFiles
41
from bzrlib.smart import client, vfs
44
from bzrlib.smart import client, vfs, repository as smart_repo
42
45
from bzrlib.revision import ensure_null, NULL_REVISION
43
46
from bzrlib.trace import mutter, note, warning
49
class _RpcHelper(object):
50
"""Mixin class that helps with issuing RPCs."""
52
def _call(self, method, *args, **err_context):
54
return self._client.call(method, *args)
55
except errors.ErrorFromSmartServer, err:
56
self._translate_error(err, **err_context)
58
def _call_expecting_body(self, method, *args, **err_context):
60
return self._client.call_expecting_body(method, *args)
61
except errors.ErrorFromSmartServer, err:
62
self._translate_error(err, **err_context)
64
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
66
return self._client.call_with_body_bytes(method, args, body_bytes)
67
except errors.ErrorFromSmartServer, err:
68
self._translate_error(err, **err_context)
70
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
73
return self._client.call_with_body_bytes_expecting_body(
74
method, args, body_bytes)
75
except errors.ErrorFromSmartServer, err:
76
self._translate_error(err, **err_context)
79
def response_tuple_to_repo_format(response):
80
"""Convert a response tuple describing a repository format to a format."""
81
format = RemoteRepositoryFormat()
82
format._rich_root_data = (response[0] == 'yes')
83
format._supports_tree_reference = (response[1] == 'yes')
84
format._supports_external_lookups = (response[2] == 'yes')
85
format._network_name = response[3]
46
89
# Note: RemoteBzrDirFormat is in bzrdir.py
48
class RemoteBzrDir(BzrDir):
91
class RemoteBzrDir(BzrDir, _RpcHelper):
49
92
"""Control directory on a remote server, accessed via bzr:// or similar."""
51
def __init__(self, transport, _client=None):
94
def __init__(self, transport, format, _client=None, _force_probe=False):
52
95
"""Construct a RemoteBzrDir.
54
97
:param _client: Private parameter for testing. Disables probing and the
55
98
use of a real bzrdir.
57
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
100
BzrDir.__init__(self, transport, format)
58
101
# this object holds a delegated bzrdir that uses file-level operations
59
102
# to talk to the other side
60
103
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
62
109
if _client is None:
63
110
medium = transport.get_smart_medium()
64
111
self._client = client._SmartClient(medium)
66
113
self._client = _client
120
return '%s(%r)' % (self.__class__.__name__, self._client)
122
def _probe_bzrdir(self):
123
medium = self._client._medium
69
124
path = self._path_for_remote_call(self._client)
70
response = self._client.call('BzrDir.open', path)
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)
71
151
if response not in [('yes',), ('no',)]:
72
152
raise errors.UnexpectedSmartServerResponse(response)
73
153
if response == ('no',):
74
raise errors.NotBranchError(path=transport.base)
154
raise errors.NotBranchError(path=self.root_transport.base)
76
156
def _ensure_real(self):
77
157
"""Ensure that there is a _real_bzrdir set.
79
159
Used before calls to self._real_bzrdir.
81
161
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()))
82
166
self._real_bzrdir = BzrDir.open_from_transport(
83
167
self.root_transport, _server_formats=False)
85
def cloning_metadir(self, stacked=False):
87
return self._real_bzrdir.cloning_metadir(stacked)
168
self._format._network_name = \
169
self._real_bzrdir._format.network_name()
89
171
def _translate_error(self, err, **context):
90
172
_translate_error(err, bzrdir=self, **context)
174
def break_lock(self):
175
# Prevent aliasing problems in the next_open_branch_result cache.
176
# See create_branch for rationale.
177
self._next_open_branch_result = None
178
return BzrDir.break_lock(self)
180
def _vfs_cloning_metadir(self, require_stacking=False):
182
return self._real_bzrdir.cloning_metadir(
183
require_stacking=require_stacking)
185
def cloning_metadir(self, require_stacking=False):
186
medium = self._client._medium
187
if medium._is_remote_before((1, 13)):
188
return self._vfs_cloning_metadir(require_stacking=require_stacking)
189
verb = 'BzrDir.cloning_metadir'
194
path = self._path_for_remote_call(self._client)
196
response = self._call(verb, path, stacking)
197
except errors.UnknownSmartMethod:
198
medium._remember_remote_is_before((1, 13))
199
return self._vfs_cloning_metadir(require_stacking=require_stacking)
200
except errors.UnknownErrorFromSmartServer, err:
201
if err.error_tuple != ('BranchReference',):
203
# We need to resolve the branch reference to determine the
204
# cloning_metadir. This causes unnecessary RPCs to open the
205
# referenced branch (and bzrdir, etc) but only when the caller
206
# didn't already resolve the branch reference.
207
referenced_branch = self.open_branch()
208
return referenced_branch.bzrdir.cloning_metadir()
209
if len(response) != 3:
210
raise errors.UnexpectedSmartServerResponse(response)
211
control_name, repo_name, branch_info = response
212
if len(branch_info) != 2:
213
raise errors.UnexpectedSmartServerResponse(response)
214
branch_ref, branch_name = branch_info
215
format = bzrdir.network_format_registry.get(control_name)
217
format.repository_format = repository.network_format_registry.get(
219
if branch_ref == 'ref':
220
# XXX: we need possible_transports here to avoid reopening the
221
# connection to the referenced location
222
ref_bzrdir = BzrDir.open(branch_name)
223
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
224
format.set_branch_format(branch_format)
225
elif branch_ref == 'branch':
227
format.set_branch_format(
228
branch.network_format_registry.get(branch_name))
230
raise errors.UnexpectedSmartServerResponse(response)
92
233
def create_repository(self, shared=False):
94
self._real_bzrdir.create_repository(shared=shared)
95
return self.open_repository()
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()
97
242
def destroy_repository(self):
98
243
"""See BzrDir.destroy_repository"""
99
244
self._ensure_real()
100
245
self._real_bzrdir.destroy_repository()
102
def create_branch(self):
104
real_branch = self._real_bzrdir.create_branch()
105
return RemoteBranch(self, self.find_repository(), real_branch)
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
107
def destroy_branch(self):
266
def destroy_branch(self, name=None):
108
267
"""See BzrDir.destroy_branch"""
109
268
self._ensure_real()
110
self._real_bzrdir.destroy_branch()
269
self._real_bzrdir.destroy_branch(name=name)
270
self._next_open_branch_result = None
112
272
def create_workingtree(self, revision_id=None, from_branch=None):
113
273
raise errors.NotLocalUrl(self.transport.base)
123
283
def get_branch_reference(self):
124
284
"""See BzrDir.get_branch_reference()."""
285
response = self._get_branch_reference()
286
if response[0] == 'ref':
291
def _get_branch_reference(self):
125
292
path = self._path_for_remote_call(self._client)
127
response = self._client.call('BzrDir.open_branch', path)
128
except errors.ErrorFromSmartServer, err:
129
self._translate_error(err)
130
if response[0] == 'ok':
131
if response[1] == '':
132
# branch at this location.
135
# a branch reference, use the existing BranchReference logic.
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'):
138
318
raise errors.UnexpectedSmartServerResponse(response)
140
321
def _get_tree_branch(self):
141
322
"""See BzrDir._get_tree_branch()."""
142
323
return None, self.open_branch()
144
def open_branch(self, _unsupported=False):
325
def open_branch(self, name=None, unsupported=False,
326
ignore_fallbacks=False):
146
328
raise NotImplementedError('unsupported flag support not implemented yet.')
147
reference_url = self.get_branch_reference()
148
if reference_url is None:
149
# branch at this location.
150
return RemoteBranch(self, self.find_repository())
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':
152
336
# a branch reference, use the existing BranchReference logic.
153
337
format = BranchReferenceFormat()
154
return format.open(self, _found=True, location=reference_url)
338
return format.open(self, name=name, _found=True,
339
location=response[1], ignore_fallbacks=ignore_fallbacks)
340
branch_format_name = response[1]
341
if not branch_format_name:
342
branch_format_name = None
343
format = RemoteBranchFormat(network_name=branch_format_name)
344
return RemoteBranch(self, self.find_repository(), format=format,
345
setup_stacking=not ignore_fallbacks, name=name)
347
def _open_repo_v1(self, path):
348
verb = 'BzrDir.find_repository'
349
response = self._call(verb, path)
350
if response[0] != 'ok':
351
raise errors.UnexpectedSmartServerResponse(response)
352
# servers that only support the v1 method don't support external
355
repo = self._real_bzrdir.open_repository()
356
response = response + ('no', repo._format.network_name())
357
return response, repo
359
def _open_repo_v2(self, path):
360
verb = 'BzrDir.find_repositoryV2'
361
response = self._call(verb, path)
362
if response[0] != 'ok':
363
raise errors.UnexpectedSmartServerResponse(response)
365
repo = self._real_bzrdir.open_repository()
366
response = response + (repo._format.network_name(),)
367
return response, repo
369
def _open_repo_v3(self, path):
370
verb = 'BzrDir.find_repositoryV3'
371
medium = self._client._medium
372
if medium._is_remote_before((1, 13)):
373
raise errors.UnknownSmartMethod(verb)
375
response = self._call(verb, path)
376
except errors.UnknownSmartMethod:
377
medium._remember_remote_is_before((1, 13))
379
if response[0] != 'ok':
380
raise errors.UnexpectedSmartServerResponse(response)
381
return response, None
156
383
def open_repository(self):
157
384
path = self._path_for_remote_call(self._client)
158
verb = 'BzrDir.find_repositoryV2'
386
for probe in [self._open_repo_v3, self._open_repo_v2,
161
response = self._client.call(verb, path)
389
response, real_repo = probe(path)
162
391
except errors.UnknownSmartMethod:
163
verb = 'BzrDir.find_repository'
164
response = self._client.call(verb, path)
165
except errors.ErrorFromSmartServer, err:
166
self._translate_error(err)
394
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
167
395
if response[0] != 'ok':
168
396
raise errors.UnexpectedSmartServerResponse(response)
169
if verb == 'BzrDir.find_repository':
170
# servers that don't support the V2 method don't support external
172
response = response + ('no', )
173
if not (len(response) == 5):
397
if len(response) != 6:
174
398
raise SmartProtocolError('incorrect response length %s' % (response,))
175
399
if response[1] == '':
176
format = RemoteRepositoryFormat()
177
format.rich_root_data = (response[2] == 'yes')
178
format.supports_tree_reference = (response[3] == 'yes')
179
# No wire format to check this yet.
180
format.supports_external_lookups = (response[4] == 'yes')
400
# repo is at this dir.
401
format = response_tuple_to_repo_format(response[2:])
181
402
# Used to support creating a real format instance when needed.
182
403
format._creating_bzrdir = self
183
return RemoteRepository(self, format)
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)
185
410
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
187
418
def open_workingtree(self, recommend_upgrade=True):
189
if self._real_bzrdir.has_workingtree():
419
if self.has_workingtree():
190
420
raise errors.NotLocalUrl(self.root_transport)
192
422
raise errors.NoWorkingTree(self.root_transport.base)
237
469
the attributes rich_root_data and supports_tree_reference are set
238
470
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.
242
481
_matchingbzrdir = RemoteBzrDirFormat()
244
def initialize(self, a_bzrdir, shared=False):
245
if not isinstance(a_bzrdir, RemoteBzrDir):
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
246
540
prior_repo = self._creating_bzrdir.open_repository()
247
541
prior_repo._ensure_real()
248
return prior_repo._real_repository._format.initialize(
542
result = prior_repo._real_repository._format.initialize(
249
543
a_bzrdir, shared=shared)
250
return a_bzrdir.create_repository(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
252
597
def open(self, a_bzrdir):
253
598
if not isinstance(a_bzrdir, RemoteBzrDir):
254
599
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
255
600
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
257
622
def get_format_description(self):
258
return 'bzr remote repository'
624
return 'Remote: ' + self._custom_format.get_format_description()
260
626
def __eq__(self, other):
261
return self.__class__ == other.__class__
263
def check_conversion_target(self, target_format):
264
if self.rich_root_data and not target_format.rich_root_data:
265
raise errors.BadConversionTarget(
266
'Does not support rich root data.', target_format)
267
if (self.supports_tree_reference and
268
not getattr(target_format, 'supports_tree_reference', False)):
269
raise errors.BadConversionTarget(
270
'Does not support nested trees', target_format)
273
class RemoteRepository(object):
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):
274
647
"""Repository accessed over rpc.
276
649
For the moment most operations are performed using local transport-backed
345
732
self._ensure_real()
346
733
return self._real_repository.commit_write_group()
735
def resume_write_group(self, tokens):
737
return self._real_repository.resume_write_group(tokens)
739
def suspend_write_group(self):
741
return self._real_repository.suspend_write_group()
743
def get_missing_parent_inventories(self, check_for_missing_texts=True):
745
return self._real_repository.get_missing_parent_inventories(
746
check_for_missing_texts=check_for_missing_texts)
748
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
750
return self._real_repository.get_rev_id_for_revno(
753
def get_rev_id_for_revno(self, revno, known_pair):
754
"""See Repository.get_rev_id_for_revno."""
755
path = self.bzrdir._path_for_remote_call(self._client)
757
if self._client._medium._is_remote_before((1, 17)):
758
return self._get_rev_id_for_revno_vfs(revno, known_pair)
759
response = self._call(
760
'Repository.get_rev_id_for_revno', path, revno, known_pair)
761
except errors.UnknownSmartMethod:
762
self._client._medium._remember_remote_is_before((1, 17))
763
return self._get_rev_id_for_revno_vfs(revno, known_pair)
764
if response[0] == 'ok':
765
return True, response[1]
766
elif response[0] == 'history-incomplete':
767
known_pair = response[1:3]
768
for fallback in self._fallback_repositories:
769
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
774
# Not found in any fallbacks
775
return False, known_pair
777
raise errors.UnexpectedSmartServerResponse(response)
348
779
def _ensure_real(self):
349
780
"""Ensure that there is a _real_repository set.
351
782
Used before calls to self._real_repository.
784
Note that _ensure_real causes many roundtrips to the server which are
785
not desirable, and prevents the use of smart one-roundtrip RPC's to
786
perform complex operations (such as accessing parent data, streaming
787
revisions etc). Adding calls to _ensure_real should only be done when
788
bringing up new functionality, adding fallbacks for smart methods that
789
require a fallback path, and never to replace an existing smart method
790
invocation. If in doubt chat to the bzr network team.
353
792
if self._real_repository is None:
793
if 'hpssvfs' in debug.debug_flags:
795
warning('VFS Repository access triggered\n%s',
796
''.join(traceback.format_stack()))
797
self._unstacked_provider.missing_keys.clear()
354
798
self.bzrdir._ensure_real()
355
799
self._set_real_repository(
356
800
self.bzrdir._real_bzrdir.open_repository())
413
849
for line in lines:
414
850
d = tuple(line.split())
415
851
revision_graph[d[0]] = d[1:]
417
853
return revision_graph
856
"""See Repository._get_sink()."""
857
return RemoteStreamSink(self)
859
def _get_source(self, to_format):
860
"""Return a source for streaming from this repository."""
861
return RemoteStreamSource(self, to_format)
419
864
def has_revision(self, revision_id):
420
"""See Repository.has_revision()."""
421
if revision_id == NULL_REVISION:
422
# The null revision is always present.
424
path = self.bzrdir._path_for_remote_call(self._client)
425
response = self._client.call(
426
'Repository.has_revision', path, revision_id)
427
if response[0] not in ('yes', 'no'):
428
raise errors.UnexpectedSmartServerResponse(response)
429
if response[0] == 'yes':
431
for fallback_repo in self._fallback_repositories:
432
if fallback_repo.has_revision(revision_id):
865
"""True if this repository has a copy of the revision."""
866
# Copy of bzrlib.repository.Repository.has_revision
867
return revision_id in self.has_revisions((revision_id,))
436
870
def has_revisions(self, revision_ids):
437
"""See Repository.has_revisions()."""
438
# FIXME: This does many roundtrips, particularly when there are
439
# fallback repositories. -- mbp 20080905
441
for revision_id in revision_ids:
442
if self.has_revision(revision_id):
443
result.add(revision_id)
871
"""Probe to find out the presence of multiple revisions.
873
:param revision_ids: An iterable of revision_ids.
874
:return: A set of the revision_ids that were present.
876
# Copy of bzrlib.repository.Repository.has_revisions
877
parent_map = self.get_parent_map(revision_ids)
878
result = set(parent_map)
879
if _mod_revision.NULL_REVISION in revision_ids:
880
result.add(_mod_revision.NULL_REVISION)
883
def _has_same_fallbacks(self, other_repo):
884
"""Returns true if the repositories have the same fallbacks."""
885
# XXX: copied from Repository; it should be unified into a base class
886
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
887
my_fb = self._fallback_repositories
888
other_fb = other_repo._fallback_repositories
889
if len(my_fb) != len(other_fb):
891
for f, g in zip(my_fb, other_fb):
892
if not f.has_same_location(g):
446
896
def has_same_location(self, other):
447
return (self.__class__ == other.__class__ and
897
# TODO: Move to RepositoryBase and unify with the regular Repository
898
# one; unfortunately the tests rely on slightly different behaviour at
899
# present -- mbp 20090710
900
return (self.__class__ is other.__class__ and
448
901
self.bzrdir.transport.base == other.bzrdir.transport.base)
450
903
def get_graph(self, other_repository=None):
451
904
"""Return the graph for this repository format"""
452
parents_provider = self
453
if (other_repository is not None and
454
other_repository.bzrdir.transport.base !=
455
self.bzrdir.transport.base):
456
parents_provider = graph._StackedParentsProvider(
457
[parents_provider, other_repository._make_parents_provider()])
905
parents_provider = self._make_parents_provider(other_repository)
458
906
return graph.Graph(parents_provider)
909
def get_known_graph_ancestry(self, revision_ids):
910
"""Return the known graph for a set of revision ids and their ancestors.
912
st = static_tuple.StaticTuple
913
revision_keys = [st(r_id).intern() for r_id in revision_ids]
914
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
915
return graph.GraphThunkIdsToKeys(known_graph)
460
917
def gather_stats(self, revid=None, committers=None):
461
918
"""See Repository.gather_stats()."""
462
919
path = self.bzrdir._path_for_remote_call(self._client)
723
1215
def add_fallback_repository(self, repository):
724
1216
"""Add a repository to use for looking up data not held locally.
726
1218
:param repository: A repository.
728
# XXX: At the moment the RemoteRepository will allow fallbacks
729
# unconditionally - however, a _real_repository will usually exist,
730
# and may raise an error if it's not accommodated by the underlying
731
# format. Eventually we should check when opening the repository
732
# whether it's willing to allow them or not.
1220
if not self._format.supports_external_lookups:
1221
raise errors.UnstackableRepositoryFormat(
1222
self._format.network_name(), self.base)
734
1223
# We need to accumulate additional repositories here, to pass them in
735
1224
# on various RPC's.
1226
if self.is_locked():
1227
# We will call fallback.unlock() when we transition to the unlocked
1228
# state, so always add a lock here. If a caller passes us a locked
1229
# repository, they are responsible for unlocking it later.
1230
repository.lock_read()
1231
self._check_fallback_repository(repository)
736
1232
self._fallback_repositories.append(repository)
737
# They are also seen by the fallback repository. If it doesn't exist
738
# yet they'll be added then. This implicitly copies them.
1233
# If self._real_repository was parameterised already (e.g. because a
1234
# _real_branch had its get_stacked_on_url method called), then the
1235
# repository to be added may already be in the _real_repositories list.
1236
if self._real_repository is not None:
1237
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1238
self._real_repository._fallback_repositories]
1239
if repository.bzrdir.root_transport.base not in fallback_locations:
1240
self._real_repository.add_fallback_repository(repository)
1242
def _check_fallback_repository(self, repository):
1243
"""Check that this repository can fallback to repository safely.
1245
Raise an error if not.
1247
:param repository: A repository to fallback to.
1249
return _mod_repository.InterRepository._assert_same_model(
741
1252
def add_inventory(self, revid, inv, parents):
742
1253
self._ensure_real()
743
1254
return self._real_repository.add_inventory(revid, inv, parents)
1256
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1257
parents, basis_inv=None, propagate_caches=False):
1259
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1260
delta, new_revision_id, parents, basis_inv=basis_inv,
1261
propagate_caches=propagate_caches)
745
1263
def add_revision(self, rev_id, rev, inv=None, config=None):
746
1264
self._ensure_real()
747
1265
return self._real_repository.add_revision(
828
1375
self._ensure_real()
829
1376
return self._real_repository._get_versioned_file_checker(
830
1377
revisions, revision_versions_cache)
832
1379
def iter_files_bytes(self, desired_files):
833
1380
"""See Repository.iter_file_bytes.
835
1382
self._ensure_real()
836
1383
return self._real_repository.iter_files_bytes(desired_files)
839
def _fetch_order(self):
840
"""Decorate the real repository for now.
842
In the long term getting this back from the remote repository as part
843
of open would be more efficient.
846
return self._real_repository._fetch_order
849
def _fetch_uses_deltas(self):
850
"""Decorate the real repository for now.
852
In the long term getting this back from the remote repository as part
853
of open would be more efficient.
856
return self._real_repository._fetch_uses_deltas
859
def _fetch_reconcile(self):
860
"""Decorate the real repository for now.
862
In the long term getting this back from the remote repository as part
863
of open would be more efficient.
866
return self._real_repository._fetch_reconcile
868
def get_parent_map(self, keys):
1385
def get_parent_map(self, revision_ids):
869
1386
"""See bzrlib.Graph.get_parent_map()."""
870
# Hack to build up the caching logic.
871
ancestry = self._parents_map
873
# Repository is not locked, so there's no cache.
874
missing_revisions = set(keys)
877
missing_revisions = set(key for key in keys if key not in ancestry)
878
if missing_revisions:
879
parent_map = self._get_parent_map(missing_revisions)
880
if 'hpss' in debug.debug_flags:
881
mutter('retransmitted revisions: %d of %d',
882
len(set(ancestry).intersection(parent_map)),
884
ancestry.update(parent_map)
885
present_keys = [k for k in keys if k in ancestry]
886
if 'hpss' in debug.debug_flags:
887
if self._requested_parents is not None and len(ancestry) != 0:
888
self._requested_parents.update(present_keys)
889
mutter('Current RemoteRepository graph hit rate: %d%%',
890
100.0 * len(self._requested_parents) / len(ancestry))
891
return dict((k, ancestry[k]) for k in present_keys)
1387
return self._make_parents_provider().get_parent_map(revision_ids)
893
def _get_parent_map(self, keys):
1389
def _get_parent_map_rpc(self, keys):
894
1390
"""Helper for get_parent_map that performs the RPC."""
895
1391
medium = self._client._medium
896
1392
if medium._is_remote_before((1, 2)):
897
1393
# We already found out that the server can't understand
898
1394
# Repository.get_parent_map requests, so just fetch the whole
900
# XXX: Note that this will issue a deprecation warning. This is ok
901
# :- its because we're working with a deprecated server anyway, and
902
# the user will almost certainly have seen a warning about the
903
# server version already.
904
rg = self.get_revision_graph()
905
# There is an api discrepency between get_parent_map and
1397
# Note that this reads the whole graph, when only some keys are
1398
# wanted. On this old server there's no way (?) to get them all
1399
# in one go, and the user probably will have seen a warning about
1400
# the server being old anyhow.
1401
rg = self._get_revision_graph(None)
1402
# There is an API discrepancy between get_parent_map and
906
1403
# get_revision_graph. Specifically, a "key:()" pair in
907
1404
# get_revision_graph just means a node has no parents. For
908
1405
# "get_parent_map" it means the node is a ghost. So fix up the
1199
1730
:param recipe: A search recipe (start, stop, count).
1200
1731
:return: Serialised bytes.
1202
start_keys = ' '.join(recipe[0])
1203
stop_keys = ' '.join(recipe[1])
1204
count = str(recipe[2])
1733
start_keys = ' '.join(recipe[1])
1734
stop_keys = ' '.join(recipe[2])
1735
count = str(recipe[3])
1205
1736
return '\n'.join((start_keys, stop_keys, count))
1738
def _serialise_search_result(self, search_result):
1739
if isinstance(search_result, graph.PendingAncestryResult):
1740
parts = ['ancestry-of']
1741
parts.extend(search_result.heads)
1743
recipe = search_result.get_recipe()
1744
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1745
return '\n'.join(parts)
1748
path = self.bzrdir._path_for_remote_call(self._client)
1750
response = self._call('PackRepository.autopack', path)
1751
except errors.UnknownSmartMethod:
1753
self._real_repository._pack_collection.autopack()
1756
if response[0] != 'ok':
1757
raise errors.UnexpectedSmartServerResponse(response)
1760
class RemoteStreamSink(repository.StreamSink):
1762
def _insert_real(self, stream, src_format, resume_tokens):
1763
self.target_repo._ensure_real()
1764
sink = self.target_repo._real_repository._get_sink()
1765
result = sink.insert_stream(stream, src_format, resume_tokens)
1767
self.target_repo.autopack()
1770
def insert_stream(self, stream, src_format, resume_tokens):
1771
target = self.target_repo
1772
target._unstacked_provider.missing_keys.clear()
1773
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1774
if target._lock_token:
1775
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1776
lock_args = (target._lock_token or '',)
1778
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1780
client = target._client
1781
medium = client._medium
1782
path = target.bzrdir._path_for_remote_call(client)
1783
# Probe for the verb to use with an empty stream before sending the
1784
# real stream to it. We do this both to avoid the risk of sending a
1785
# large request that is then rejected, and because we don't want to
1786
# implement a way to buffer, rewind, or restart the stream.
1788
for verb, required_version in candidate_calls:
1789
if medium._is_remote_before(required_version):
1792
# We've already done the probing (and set _is_remote_before) on
1793
# a previous insert.
1796
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1798
response = client.call_with_body_stream(
1799
(verb, path, '') + lock_args, byte_stream)
1800
except errors.UnknownSmartMethod:
1801
medium._remember_remote_is_before(required_version)
1807
return self._insert_real(stream, src_format, resume_tokens)
1808
self._last_inv_record = None
1809
self._last_substream = None
1810
if required_version < (1, 19):
1811
# Remote side doesn't support inventory deltas. Wrap the stream to
1812
# make sure we don't send any. If the stream contains inventory
1813
# deltas we'll interrupt the smart insert_stream request and
1815
stream = self._stop_stream_if_inventory_delta(stream)
1816
byte_stream = smart_repo._stream_to_byte_stream(
1818
resume_tokens = ' '.join(resume_tokens)
1819
response = client.call_with_body_stream(
1820
(verb, path, resume_tokens) + lock_args, byte_stream)
1821
if response[0][0] not in ('ok', 'missing-basis'):
1822
raise errors.UnexpectedSmartServerResponse(response)
1823
if self._last_substream is not None:
1824
# The stream included an inventory-delta record, but the remote
1825
# side isn't new enough to support them. So we need to send the
1826
# rest of the stream via VFS.
1827
self.target_repo.refresh_data()
1828
return self._resume_stream_with_vfs(response, src_format)
1829
if response[0][0] == 'missing-basis':
1830
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1831
resume_tokens = tokens
1832
return resume_tokens, set(missing_keys)
1834
self.target_repo.refresh_data()
1837
def _resume_stream_with_vfs(self, response, src_format):
1838
"""Resume sending a stream via VFS, first resending the record and
1839
substream that couldn't be sent via an insert_stream verb.
1841
if response[0][0] == 'missing-basis':
1842
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1843
# Ignore missing_keys, we haven't finished inserting yet
1846
def resume_substream():
1847
# Yield the substream that was interrupted.
1848
for record in self._last_substream:
1850
self._last_substream = None
1851
def resume_stream():
1852
# Finish sending the interrupted substream
1853
yield ('inventory-deltas', resume_substream())
1854
# Then simply continue sending the rest of the stream.
1855
for substream_kind, substream in self._last_stream:
1856
yield substream_kind, substream
1857
return self._insert_real(resume_stream(), src_format, tokens)
1859
def _stop_stream_if_inventory_delta(self, stream):
1860
"""Normally this just lets the original stream pass-through unchanged.
1862
However if any 'inventory-deltas' substream occurs it will stop
1863
streaming, and store the interrupted substream and stream in
1864
self._last_substream and self._last_stream so that the stream can be
1865
resumed by _resume_stream_with_vfs.
1868
stream_iter = iter(stream)
1869
for substream_kind, substream in stream_iter:
1870
if substream_kind == 'inventory-deltas':
1871
self._last_substream = substream
1872
self._last_stream = stream_iter
1875
yield substream_kind, substream
1878
class RemoteStreamSource(repository.StreamSource):
1879
"""Stream data from a remote server."""
1881
def get_stream(self, search):
1882
if (self.from_repository._fallback_repositories and
1883
self.to_format._fetch_order == 'topological'):
1884
return self._real_stream(self.from_repository, search)
1887
repos = [self.from_repository]
1893
repos.extend(repo._fallback_repositories)
1894
sources.append(repo)
1895
return self.missing_parents_chain(search, sources)
1897
def get_stream_for_missing_keys(self, missing_keys):
1898
self.from_repository._ensure_real()
1899
real_repo = self.from_repository._real_repository
1900
real_source = real_repo._get_source(self.to_format)
1901
return real_source.get_stream_for_missing_keys(missing_keys)
1903
def _real_stream(self, repo, search):
1904
"""Get a stream for search from repo.
1906
This never called RemoteStreamSource.get_stream, and is a heler
1907
for RemoteStreamSource._get_stream to allow getting a stream
1908
reliably whether fallback back because of old servers or trying
1909
to stream from a non-RemoteRepository (which the stacked support
1912
source = repo._get_source(self.to_format)
1913
if isinstance(source, RemoteStreamSource):
1915
source = repo._real_repository._get_source(self.to_format)
1916
return source.get_stream(search)
1918
def _get_stream(self, repo, search):
1919
"""Core worker to get a stream from repo for search.
1921
This is used by both get_stream and the stacking support logic. It
1922
deliberately gets a stream for repo which does not need to be
1923
self.from_repository. In the event that repo is not Remote, or
1924
cannot do a smart stream, a fallback is made to the generic
1925
repository._get_stream() interface, via self._real_stream.
1927
In the event of stacking, streams from _get_stream will not
1928
contain all the data for search - this is normal (see get_stream).
1930
:param repo: A repository.
1931
:param search: A search.
1933
# Fallbacks may be non-smart
1934
if not isinstance(repo, RemoteRepository):
1935
return self._real_stream(repo, search)
1936
client = repo._client
1937
medium = client._medium
1938
path = repo.bzrdir._path_for_remote_call(client)
1939
search_bytes = repo._serialise_search_result(search)
1940
args = (path, self.to_format.network_name())
1942
('Repository.get_stream_1.19', (1, 19)),
1943
('Repository.get_stream', (1, 13))]
1945
for verb, version in candidate_verbs:
1946
if medium._is_remote_before(version):
1949
response = repo._call_with_body_bytes_expecting_body(
1950
verb, args, search_bytes)
1951
except errors.UnknownSmartMethod:
1952
medium._remember_remote_is_before(version)
1954
response_tuple, response_handler = response
1958
return self._real_stream(repo, search)
1959
if response_tuple[0] != 'ok':
1960
raise errors.UnexpectedSmartServerResponse(response_tuple)
1961
byte_stream = response_handler.read_streamed_body()
1962
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1963
if src_format.network_name() != repo._format.network_name():
1964
raise AssertionError(
1965
"Mismatched RemoteRepository and stream src %r, %r" % (
1966
src_format.network_name(), repo._format.network_name()))
1969
def missing_parents_chain(self, search, sources):
1970
"""Chain multiple streams together to handle stacking.
1972
:param search: The overall search to satisfy with streams.
1973
:param sources: A list of Repository objects to query.
1975
self.from_serialiser = self.from_repository._format._serializer
1976
self.seen_revs = set()
1977
self.referenced_revs = set()
1978
# If there are heads in the search, or the key count is > 0, we are not
1980
while not search.is_empty() and len(sources) > 1:
1981
source = sources.pop(0)
1982
stream = self._get_stream(source, search)
1983
for kind, substream in stream:
1984
if kind != 'revisions':
1985
yield kind, substream
1987
yield kind, self.missing_parents_rev_handler(substream)
1988
search = search.refine(self.seen_revs, self.referenced_revs)
1989
self.seen_revs = set()
1990
self.referenced_revs = set()
1991
if not search.is_empty():
1992
for kind, stream in self._get_stream(sources[0], search):
1995
def missing_parents_rev_handler(self, substream):
1996
for content in substream:
1997
revision_bytes = content.get_bytes_as('fulltext')
1998
revision = self.from_serialiser.read_revision_from_string(
2000
self.seen_revs.add(content.key[-1])
2001
self.referenced_revs.update(revision.parent_ids)
1208
2005
class RemoteBranchLockableFiles(LockableFiles):
1209
2006
"""A 'LockableFiles' implementation that talks to a smart server.
1211
2008
This is not a public interface class.
1228
2025
class RemoteBranchFormat(branch.BranchFormat):
2027
def __init__(self, network_name=None):
2028
super(RemoteBranchFormat, self).__init__()
2029
self._matchingbzrdir = RemoteBzrDirFormat()
2030
self._matchingbzrdir.set_branch_format(self)
2031
self._custom_format = None
2032
self._network_name = network_name
1230
2034
def __eq__(self, other):
1231
return (isinstance(other, RemoteBranchFormat) and
2035
return (isinstance(other, RemoteBranchFormat) and
1232
2036
self.__dict__ == other.__dict__)
2038
def _ensure_real(self):
2039
if self._custom_format is None:
2040
self._custom_format = branch.network_format_registry.get(
1234
2043
def get_format_description(self):
1235
return 'Remote BZR Branch'
1237
def get_format_string(self):
1238
return 'Remote BZR Branch'
1240
def open(self, a_bzrdir):
1241
return a_bzrdir.open_branch()
1243
def initialize(self, a_bzrdir):
1244
return a_bzrdir.create_branch()
2045
return 'Remote: ' + self._custom_format.get_format_description()
2047
def network_name(self):
2048
return self._network_name
2050
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2051
return a_bzrdir.open_branch(name=name,
2052
ignore_fallbacks=ignore_fallbacks)
2054
def _vfs_initialize(self, a_bzrdir, name):
2055
# Initialisation when using a local bzrdir object, or a non-vfs init
2056
# method is not available on the server.
2057
# self._custom_format is always set - the start of initialize ensures
2059
if isinstance(a_bzrdir, RemoteBzrDir):
2060
a_bzrdir._ensure_real()
2061
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2064
# We assume the bzrdir is parameterised; it may not be.
2065
result = self._custom_format.initialize(a_bzrdir, name)
2066
if (isinstance(a_bzrdir, RemoteBzrDir) and
2067
not isinstance(result, RemoteBranch)):
2068
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2072
def initialize(self, a_bzrdir, name=None):
2073
# 1) get the network name to use.
2074
if self._custom_format:
2075
network_name = self._custom_format.network_name()
2077
# Select the current bzrlib default and ask for that.
2078
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2079
reference_format = reference_bzrdir_format.get_branch_format()
2080
self._custom_format = reference_format
2081
network_name = reference_format.network_name()
2082
# Being asked to create on a non RemoteBzrDir:
2083
if not isinstance(a_bzrdir, RemoteBzrDir):
2084
return self._vfs_initialize(a_bzrdir, name=name)
2085
medium = a_bzrdir._client._medium
2086
if medium._is_remote_before((1, 13)):
2087
return self._vfs_initialize(a_bzrdir, name=name)
2088
# Creating on a remote bzr dir.
2089
# 2) try direct creation via RPC
2090
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2091
if name is not None:
2092
# XXX JRV20100304: Support creating colocated branches
2093
raise errors.NoColocatedBranchSupport(self)
2094
verb = 'BzrDir.create_branch'
2096
response = a_bzrdir._call(verb, path, network_name)
2097
except errors.UnknownSmartMethod:
2098
# Fallback - use vfs methods
2099
medium._remember_remote_is_before((1, 13))
2100
return self._vfs_initialize(a_bzrdir, name=name)
2101
if response[0] != 'ok':
2102
raise errors.UnexpectedSmartServerResponse(response)
2103
# Turn the response into a RemoteRepository object.
2104
format = RemoteBranchFormat(network_name=response[1])
2105
repo_format = response_tuple_to_repo_format(response[3:])
2106
if response[2] == '':
2107
repo_bzrdir = a_bzrdir
2109
repo_bzrdir = RemoteBzrDir(
2110
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2112
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2113
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2114
format=format, setup_stacking=False, name=name)
2115
# XXX: We know this is a new branch, so it must have revno 0, revid
2116
# NULL_REVISION. Creating the branch locked would make this be unable
2117
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2118
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2119
return remote_branch
2121
def make_tags(self, branch):
2123
return self._custom_format.make_tags(branch)
1246
2125
def supports_tags(self):
1247
2126
# Remote branches might support tags, but we won't know until we
1248
2127
# access the real remote branch.
1252
class RemoteBranch(branch.Branch):
2129
return self._custom_format.supports_tags()
2131
def supports_stacking(self):
2133
return self._custom_format.supports_stacking()
2135
def supports_set_append_revisions_only(self):
2137
return self._custom_format.supports_set_append_revisions_only()
2140
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
1253
2141
"""Branch stored on a server accessed by HPSS RPC.
1255
2143
At the moment most operations are mapped down to simple file operations.
1258
2146
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2147
_client=None, format=None, setup_stacking=True, name=None):
1260
2148
"""Create a RemoteBranch instance.
1262
2150
:param real_branch: An optional local implementation of the branch
1263
2151
format, usually accessing the data via the VFS.
1264
2152
:param _client: Private parameter for testing.
2153
:param format: A RemoteBranchFormat object, None to create one
2154
automatically. If supplied it should have a network_name already
2156
:param setup_stacking: If True make an RPC call to determine the
2157
stacked (or not) status of the branch. If False assume the branch
2159
:param name: Colocated branch name
1266
2161
# We intentionally don't call the parent class's __init__, because it
1267
2162
# will try to assign to self.tags, which is a property in this subclass.
1268
2163
# And the parent's __init__ doesn't do much anyway.
1269
self._revision_id_to_revno_cache = None
1270
self._revision_history_cache = None
1271
self._last_revision_info_cache = None
1272
2164
self.bzrdir = remote_bzrdir
1273
2165
if _client is not None:
1274
2166
self._client = _client
1560
2535
def _set_last_revision_descendant(self, revision_id, other_branch,
1561
2536
allow_diverged=False, allow_overwrite_descendant=False):
1563
response = self._client.call('Branch.set_last_revision_ex',
1564
self._remote_path(), self._lock_token, self._repo_lock_token, revision_id,
1565
int(allow_diverged), int(allow_overwrite_descendant))
1566
except errors.ErrorFromSmartServer, err:
1567
self._translate_error(err, other_branch=other_branch)
2537
# This performs additional work to meet the hook contract; while its
2538
# undesirable, we have to synthesise the revno to call the hook, and
2539
# not calling the hook is worse as it means changes can't be prevented.
2540
# Having calculated this though, we can't just call into
2541
# set_last_revision_info as a simple call, because there is a set_rh
2542
# hook that some folk may still be using.
2543
old_revno, old_revid = self.last_revision_info()
2544
history = self._lefthand_history(revision_id)
2545
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2546
err_context = {'other_branch': other_branch}
2547
response = self._call('Branch.set_last_revision_ex',
2548
self._remote_path(), self._lock_token, self._repo_lock_token,
2549
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
1568
2551
self._clear_cached_state()
1569
2552
if len(response) != 3 and response[0] != 'ok':
1570
2553
raise errors.UnexpectedSmartServerResponse(response)
1571
2554
new_revno, new_revision_id = response[1:]
1572
2555
self._last_revision_info_cache = new_revno, new_revision_id
2556
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1573
2557
if self._real_branch is not None:
1574
2558
cache = new_revno, new_revision_id
1575
2559
self._real_branch._last_revision_info_cache = cache
1577
2561
def _set_last_revision(self, revision_id):
2562
old_revno, old_revid = self.last_revision_info()
2563
# This performs additional work to meet the hook contract; while its
2564
# undesirable, we have to synthesise the revno to call the hook, and
2565
# not calling the hook is worse as it means changes can't be prevented.
2566
# Having calculated this though, we can't just call into
2567
# set_last_revision_info as a simple call, because there is a set_rh
2568
# hook that some folk may still be using.
2569
history = self._lefthand_history(revision_id)
2570
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
1578
2571
self._clear_cached_state()
1580
response = self._client.call('Branch.set_last_revision',
1581
self._remote_path(), self._lock_token, self._repo_lock_token, revision_id)
1582
except errors.ErrorFromSmartServer, err:
1583
self._translate_error(err)
2572
response = self._call('Branch.set_last_revision',
2573
self._remote_path(), self._lock_token, self._repo_lock_token,
1584
2575
if response != ('ok',):
1585
2576
raise errors.UnexpectedSmartServerResponse(response)
2577
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
1587
2579
@needs_write_lock
1588
2580
def set_revision_history(self, rev_history):
1595
2587
rev_id = rev_history[-1]
1596
2588
self._set_last_revision(rev_id)
2589
for hook in branch.Branch.hooks['set_rh']:
2590
hook(self, rev_history)
1597
2591
self._cache_revision_history(rev_history)
1599
def get_parent(self):
1601
return self._real_branch.get_parent()
1603
def set_parent(self, url):
1605
return self._real_branch.set_parent(url)
1607
def set_stacked_on_url(self, stacked_location):
1608
"""Set the URL this branch is stacked against.
1610
:raises UnstackableBranchFormat: If the branch does not support
1612
:raises UnstackableRepositoryFormat: If the repository does not support
1616
return self._real_branch.set_stacked_on_url(stacked_location)
1618
def sprout(self, to_bzrdir, revision_id=None):
1619
# Like Branch.sprout, except that it sprouts a branch in the default
1620
# format, because RemoteBranches can't be created at arbitrary URLs.
1621
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1622
# to_bzrdir.create_branch...
1624
result = self._real_branch._format.initialize(to_bzrdir)
1625
self.copy_content_into(result, revision_id=revision_id)
1626
result.set_parent(self.bzrdir.root_transport.base)
2593
def _get_parent_location(self):
2594
medium = self._client._medium
2595
if medium._is_remote_before((1, 13)):
2596
return self._vfs_get_parent_location()
2598
response = self._call('Branch.get_parent', self._remote_path())
2599
except errors.UnknownSmartMethod:
2600
medium._remember_remote_is_before((1, 13))
2601
return self._vfs_get_parent_location()
2602
if len(response) != 1:
2603
raise errors.UnexpectedSmartServerResponse(response)
2604
parent_location = response[0]
2605
if parent_location == '':
2607
return parent_location
2609
def _vfs_get_parent_location(self):
2611
return self._real_branch._get_parent_location()
2613
def _set_parent_location(self, url):
2614
medium = self._client._medium
2615
if medium._is_remote_before((1, 15)):
2616
return self._vfs_set_parent_location(url)
2618
call_url = url or ''
2619
if type(call_url) is not str:
2620
raise AssertionError('url must be a str or None (%s)' % url)
2621
response = self._call('Branch.set_parent_location',
2622
self._remote_path(), self._lock_token, self._repo_lock_token,
2624
except errors.UnknownSmartMethod:
2625
medium._remember_remote_is_before((1, 15))
2626
return self._vfs_set_parent_location(url)
2628
raise errors.UnexpectedSmartServerResponse(response)
2630
def _vfs_set_parent_location(self, url):
2632
return self._real_branch._set_parent_location(url)
1629
2634
@needs_write_lock
1630
2635
def pull(self, source, overwrite=False, stop_revision=None,
1686
2695
except errors.UnknownSmartMethod:
1687
2696
medium._remember_remote_is_before((1, 6))
1688
2697
self._clear_cached_state_of_remote_branch_only()
1690
self._real_branch.generate_revision_history(
1691
revision_id, last_rev=last_rev, other_branch=other_branch)
1696
return self._real_branch.tags
2698
self.set_revision_history(self._lefthand_history(revision_id,
2699
last_rev=last_rev,other_branch=other_branch))
1698
2701
def set_push_location(self, location):
1699
2702
self._ensure_real()
1700
2703
return self._real_branch.set_push_location(location)
1703
def update_revisions(self, other, stop_revision=None, overwrite=False,
1705
"""See Branch.update_revisions."""
2706
class RemoteConfig(object):
2707
"""A Config that reads and writes from smart verbs.
2709
It is a low-level object that considers config data to be name/value pairs
2710
that may be associated with a section. Assigning meaning to the these
2711
values is done at higher levels like bzrlib.config.TreeConfig.
2714
def get_option(self, name, section=None, default=None):
2715
"""Return the value associated with a named option.
2717
:param name: The name of the value
2718
:param section: The section the option is in (if any)
2719
:param default: The value to return if the value is not set
2720
:return: The value or default value
1708
if stop_revision is None:
1709
stop_revision = other.last_revision()
1710
if revision.is_null(stop_revision):
1711
# if there are no commits, we're done.
1713
self.fetch(other, stop_revision)
1716
# Just unconditionally set the new revision. We don't care if
1717
# the branches have diverged.
1718
self._set_last_revision(stop_revision)
2723
configobj = self._get_configobj()
2725
section_obj = configobj
1720
medium = self._client._medium
1721
if not medium._is_remote_before((1, 6)):
1723
self._set_last_revision_descendant(stop_revision, other)
1725
except errors.UnknownSmartMethod:
1726
medium._remember_remote_is_before((1, 6))
1727
# Fallback for pre-1.6 servers: check for divergence
1728
# client-side, then do _set_last_revision.
1729
last_rev = revision.ensure_null(self.last_revision())
1731
graph = self.repository.get_graph()
1732
if self._check_if_descendant_or_diverged(
1733
stop_revision, last_rev, graph, other):
1734
# stop_revision is a descendant of last_rev, but we aren't
1735
# overwriting, so we're done.
1737
self._set_last_revision(stop_revision)
2728
section_obj = configobj[section]
2731
return section_obj.get(name, default)
2732
except errors.UnknownSmartMethod:
2733
return self._vfs_get_option(name, section, default)
2735
def _response_to_configobj(self, response):
2736
if len(response[0]) and response[0][0] != 'ok':
2737
raise errors.UnexpectedSmartServerResponse(response)
2738
lines = response[1].read_body_bytes().splitlines()
2739
return config.ConfigObj(lines, encoding='utf-8')
2742
class RemoteBranchConfig(RemoteConfig):
2743
"""A RemoteConfig for Branches."""
2745
def __init__(self, branch):
2746
self._branch = branch
2748
def _get_configobj(self):
2749
path = self._branch._remote_path()
2750
response = self._branch._client.call_expecting_body(
2751
'Branch.get_config_file', path)
2752
return self._response_to_configobj(response)
2754
def set_option(self, value, name, section=None):
2755
"""Set the value associated with a named option.
2757
:param value: The value to set
2758
:param name: The name of the value to set
2759
:param section: The section the option is in (if any)
2761
medium = self._branch._client._medium
2762
if medium._is_remote_before((1, 14)):
2763
return self._vfs_set_option(value, name, section)
2765
path = self._branch._remote_path()
2766
response = self._branch._client.call('Branch.set_config_option',
2767
path, self._branch._lock_token, self._branch._repo_lock_token,
2768
value.encode('utf8'), name, section or '')
2769
except errors.UnknownSmartMethod:
2770
medium._remember_remote_is_before((1, 14))
2771
return self._vfs_set_option(value, name, section)
2773
raise errors.UnexpectedSmartServerResponse(response)
2775
def _real_object(self):
2776
self._branch._ensure_real()
2777
return self._branch._real_branch
2779
def _vfs_set_option(self, value, name, section=None):
2780
return self._real_object()._get_config().set_option(
2781
value, name, section)
2784
class RemoteBzrDirConfig(RemoteConfig):
2785
"""A RemoteConfig for BzrDirs."""
2787
def __init__(self, bzrdir):
2788
self._bzrdir = bzrdir
2790
def _get_configobj(self):
2791
medium = self._bzrdir._client._medium
2792
verb = 'BzrDir.get_config_file'
2793
if medium._is_remote_before((1, 15)):
2794
raise errors.UnknownSmartMethod(verb)
2795
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2796
response = self._bzrdir._call_expecting_body(
2798
return self._response_to_configobj(response)
2800
def _vfs_get_option(self, name, section, default):
2801
return self._real_object()._get_config().get_option(
2802
name, section, default)
2804
def set_option(self, value, name, section=None):
2805
"""Set the value associated with a named option.
2807
:param value: The value to set
2808
:param name: The name of the value to set
2809
:param section: The section the option is in (if any)
2811
return self._real_object()._get_config().set_option(
2812
value, name, section)
2814
def _real_object(self):
2815
self._bzrdir._ensure_real()
2816
return self._bzrdir._real_bzrdir
1742
2820
def _extract_tar(tar, to_dir):