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
30
repository as _mod_repository,
32
revision as _mod_revision,
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
20
from cStringIO import StringIO
22
from bzrlib import branch, errors, lockdir, repository
36
23
from bzrlib.branch import BranchReferenceFormat
37
24
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
39
from bzrlib.errors import (
25
from bzrlib.config import BranchConfig, TreeConfig
26
from bzrlib.decorators import needs_read_lock, needs_write_lock
27
from bzrlib.errors import NoSuchRevision
43
28
from bzrlib.lockable_files import LockableFiles
44
from bzrlib.smart import client, vfs, repository as smart_repo
45
from bzrlib.revision import ensure_null, NULL_REVISION
46
from bzrlib.trace import mutter, note, warning
49
class _RpcHelper(object):
50
"""Mixin class that helps with issuing RPCs."""
52
def _call(self, method, *args, **err_context):
54
return self._client.call(method, *args)
55
except errors.ErrorFromSmartServer, err:
56
self._translate_error(err, **err_context)
58
def _call_expecting_body(self, method, *args, **err_context):
60
return self._client.call_expecting_body(method, *args)
61
except errors.ErrorFromSmartServer, err:
62
self._translate_error(err, **err_context)
64
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
66
return self._client.call_with_body_bytes(method, args, body_bytes)
67
except errors.ErrorFromSmartServer, err:
68
self._translate_error(err, **err_context)
70
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
73
return self._client.call_with_body_bytes_expecting_body(
74
method, args, body_bytes)
75
except errors.ErrorFromSmartServer, err:
76
self._translate_error(err, **err_context)
79
def response_tuple_to_repo_format(response):
80
"""Convert a response tuple describing a repository format to a format."""
81
format = RemoteRepositoryFormat()
82
format._rich_root_data = (response[0] == 'yes')
83
format._supports_tree_reference = (response[1] == 'yes')
84
format._supports_external_lookups = (response[2] == 'yes')
85
format._network_name = response[3]
29
from bzrlib.revision import NULL_REVISION
30
from bzrlib.smart import client, vfs
31
from bzrlib.trace import note
89
33
# Note: RemoteBzrDirFormat is in bzrdir.py
91
class RemoteBzrDir(BzrDir, _RpcHelper):
35
class RemoteBzrDir(BzrDir):
92
36
"""Control directory on a remote server, accessed via bzr:// or similar."""
94
def __init__(self, transport, format, _client=None, _force_probe=False):
38
def __init__(self, transport, _client=None):
95
39
"""Construct a RemoteBzrDir.
97
41
:param _client: Private parameter for testing. Disables probing and the
98
42
use of a real bzrdir.
100
BzrDir.__init__(self, transport, format)
44
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
101
45
# this object holds a delegated bzrdir that uses file-level operations
102
46
# to talk to the other side
103
47
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
49
if _client is None:
110
medium = transport.get_smart_medium()
111
self._client = client._SmartClient(medium)
50
self._medium = transport.get_smart_client()
51
self._client = client._SmartClient(self._medium)
113
53
self._client = _client
120
return '%s(%r)' % (self.__class__.__name__, self._client)
122
def _probe_bzrdir(self):
123
medium = self._client._medium
124
58
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)
59
response = self._client.call('BzrDir.open', path)
151
60
if response not in [('yes',), ('no',)]:
152
61
raise errors.UnexpectedSmartServerResponse(response)
153
62
if response == ('no',):
154
raise errors.NotBranchError(path=self.root_transport.base)
63
raise errors.NotBranchError(path=transport.base)
156
65
def _ensure_real(self):
157
66
"""Ensure that there is a _real_bzrdir set.
159
68
Used before calls to self._real_bzrdir.
161
70
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
71
self._real_bzrdir = BzrDir.open_from_transport(
167
72
self.root_transport, _server_formats=False)
168
self._format._network_name = \
169
self._real_bzrdir._format.network_name()
171
def _translate_error(self, err, **context):
172
_translate_error(err, bzrdir=self, **context)
174
def break_lock(self):
175
# Prevent aliasing problems in the next_open_branch_result cache.
176
# See create_branch for rationale.
177
self._next_open_branch_result = None
178
return BzrDir.break_lock(self)
180
def _vfs_cloning_metadir(self, require_stacking=False):
182
return self._real_bzrdir.cloning_metadir(
183
require_stacking=require_stacking)
185
def cloning_metadir(self, require_stacking=False):
186
medium = self._client._medium
187
if medium._is_remote_before((1, 13)):
188
return self._vfs_cloning_metadir(require_stacking=require_stacking)
189
verb = 'BzrDir.cloning_metadir'
194
path = self._path_for_remote_call(self._client)
196
response = self._call(verb, path, stacking)
197
except errors.UnknownSmartMethod:
198
medium._remember_remote_is_before((1, 13))
199
return self._vfs_cloning_metadir(require_stacking=require_stacking)
200
except errors.UnknownErrorFromSmartServer, err:
201
if err.error_tuple != ('BranchReference',):
203
# We need to resolve the branch reference to determine the
204
# cloning_metadir. This causes unnecessary RPCs to open the
205
# referenced branch (and bzrdir, etc) but only when the caller
206
# didn't already resolve the branch reference.
207
referenced_branch = self.open_branch()
208
return referenced_branch.bzrdir.cloning_metadir()
209
if len(response) != 3:
210
raise errors.UnexpectedSmartServerResponse(response)
211
control_name, repo_name, branch_info = response
212
if len(branch_info) != 2:
213
raise errors.UnexpectedSmartServerResponse(response)
214
branch_ref, branch_name = branch_info
215
format = bzrdir.network_format_registry.get(control_name)
217
format.repository_format = repository.network_format_registry.get(
219
if branch_ref == 'ref':
220
# XXX: we need possible_transports here to avoid reopening the
221
# connection to the referenced location
222
ref_bzrdir = BzrDir.open(branch_name)
223
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
224
format.set_branch_format(branch_format)
225
elif branch_ref == 'branch':
227
format.set_branch_format(
228
branch.network_format_registry.get(branch_name))
230
raise errors.UnexpectedSmartServerResponse(response)
233
74
def create_repository(self, shared=False):
234
# as per meta1 formats - just delegate to the format object which may
236
result = self._format.repository_format.initialize(self, shared)
237
if not isinstance(result, RemoteRepository):
238
return self.open_repository()
242
def destroy_repository(self):
243
"""See BzrDir.destroy_repository"""
245
self._real_bzrdir.destroy_repository()
247
def create_branch(self, name=None):
248
# as per meta1 formats - just delegate to the format object which may
250
real_branch = self._format.get_branch_format().initialize(self,
252
if not isinstance(real_branch, RemoteBranch):
253
result = RemoteBranch(self, self.find_repository(), real_branch,
257
# BzrDir.clone_on_transport() uses the result of create_branch but does
258
# not return it to its callers; we save approximately 8% of our round
259
# trips by handing the branch we created back to the first caller to
260
# open_branch rather than probing anew. Long term we need a API in
261
# bzrdir that doesn't discard result objects (like result_branch).
263
self._next_open_branch_result = result
266
def destroy_branch(self, name=None):
267
"""See BzrDir.destroy_branch"""
269
self._real_bzrdir.destroy_branch(name=name)
270
self._next_open_branch_result = None
272
def create_workingtree(self, revision_id=None, from_branch=None):
76
self._real_bzrdir.create_repository(shared=shared)
77
return self.open_repository()
79
def create_branch(self):
81
real_branch = self._real_bzrdir.create_branch()
82
return RemoteBranch(self, self.find_repository(), real_branch)
84
def create_workingtree(self, revision_id=None):
273
85
raise errors.NotLocalUrl(self.transport.base)
275
87
def find_branch_format(self):
283
95
def get_branch_reference(self):
284
96
"""See BzrDir.get_branch_reference()."""
285
response = self._get_branch_reference()
286
if response[0] == 'ref':
291
def _get_branch_reference(self):
292
97
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
raise errors.UnexpectedSmartServerResponse(response)
321
def _get_tree_branch(self):
322
"""See BzrDir._get_tree_branch()."""
323
return None, self.open_branch()
325
def open_branch(self, name=None, unsupported=False,
326
ignore_fallbacks=False):
328
raise NotImplementedError('unsupported flag support not implemented yet.')
329
if self._next_open_branch_result is not None:
330
# See create_branch for details.
331
result = self._next_open_branch_result
332
self._next_open_branch_result = None
334
response = self._get_branch_reference()
335
if response[0] == 'ref':
98
response = self._client.call('BzrDir.open_branch', path)
99
if response[0] == 'ok':
100
if response[1] == '':
101
# branch at this location.
104
# a branch reference, use the existing BranchReference logic.
106
elif response == ('nobranch',):
107
raise errors.NotBranchError(path=self.root_transport.base)
109
assert False, 'unexpected response code %r' % (response,)
111
def open_branch(self, _unsupported=False):
112
assert _unsupported == False, 'unsupported flag support not implemented yet.'
113
reference_url = self.get_branch_reference()
114
if reference_url is None:
115
# branch at this location.
116
return RemoteBranch(self, self.find_repository())
336
118
# a branch reference, use the existing BranchReference logic.
337
119
format = BranchReferenceFormat()
338
return format.open(self, name=name, _found=True,
339
location=response[1], ignore_fallbacks=ignore_fallbacks)
340
branch_format_name = response[1]
341
if not branch_format_name:
342
branch_format_name = None
343
format = RemoteBranchFormat(network_name=branch_format_name)
344
return RemoteBranch(self, self.find_repository(), format=format,
345
setup_stacking=not ignore_fallbacks, name=name)
347
def _open_repo_v1(self, path):
348
verb = 'BzrDir.find_repository'
349
response = self._call(verb, path)
350
if response[0] != 'ok':
351
raise errors.UnexpectedSmartServerResponse(response)
352
# servers that only support the v1 method don't support external
355
repo = self._real_bzrdir.open_repository()
356
response = response + ('no', repo._format.network_name())
357
return response, repo
359
def _open_repo_v2(self, path):
360
verb = 'BzrDir.find_repositoryV2'
361
response = self._call(verb, path)
362
if response[0] != 'ok':
363
raise errors.UnexpectedSmartServerResponse(response)
365
repo = self._real_bzrdir.open_repository()
366
response = response + (repo._format.network_name(),)
367
return response, repo
369
def _open_repo_v3(self, path):
370
verb = 'BzrDir.find_repositoryV3'
371
medium = self._client._medium
372
if medium._is_remote_before((1, 13)):
373
raise errors.UnknownSmartMethod(verb)
375
response = self._call(verb, path)
376
except errors.UnknownSmartMethod:
377
medium._remember_remote_is_before((1, 13))
379
if response[0] != 'ok':
380
raise errors.UnexpectedSmartServerResponse(response)
381
return response, None
120
return format.open(self, _found=True, location=reference_url)
383
122
def open_repository(self):
384
123
path = self._path_for_remote_call(self._client)
386
for probe in [self._open_repo_v3, self._open_repo_v2,
389
response, real_repo = probe(path)
391
except errors.UnknownSmartMethod:
394
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
395
if response[0] != 'ok':
396
raise errors.UnexpectedSmartServerResponse(response)
397
if len(response) != 6:
398
raise SmartProtocolError('incorrect response length %s' % (response,))
124
response = self._client.call('BzrDir.find_repository', path)
125
assert response[0] in ('ok', 'norepository'), \
126
'unexpected response code %s' % (response,)
127
if response[0] == 'norepository':
128
raise errors.NoRepositoryPresent(self)
129
assert len(response) == 4, 'incorrect response length %s' % (response,)
399
130
if response[1] == '':
400
# repo is at this dir.
401
format = response_tuple_to_repo_format(response[2:])
402
# Used to support creating a real format instance when needed.
403
format._creating_bzrdir = self
404
remote_repo = RemoteRepository(self, format)
405
format._creating_repo = remote_repo
406
if real_repo is not None:
407
remote_repo._set_real_repository(real_repo)
131
format = RemoteRepositoryFormat()
132
format.rich_root_data = (response[2] == 'yes')
133
format.supports_tree_reference = (response[3] == 'yes')
134
return RemoteRepository(self, format)
410
136
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
138
def open_workingtree(self, recommend_upgrade=True):
419
if self.has_workingtree():
420
raise errors.NotLocalUrl(self.root_transport)
422
raise errors.NoWorkingTree(self.root_transport.base)
139
raise errors.NotLocalUrl(self.root_transport)
424
141
def _path_for_remote_call(self, client):
425
142
"""Return the path to be used for this bzrdir in a remote call."""
426
143
return client.remote_path_from_transport(self.root_transport)
428
def get_branch_transport(self, branch_format, name=None):
145
def get_branch_transport(self, branch_format):
429
146
self._ensure_real()
430
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
147
return self._real_bzrdir.get_branch_transport(branch_format)
432
149
def get_repository_transport(self, repository_format):
433
150
self._ensure_real()
464
174
Instances of this repository are represented by RemoteRepository
467
The RemoteRepositoryFormat is parameterized during construction
177
The RemoteRepositoryFormat is parameterised during construction
468
178
to reflect the capabilities of the real, remote format. Specifically
469
179
the attributes rich_root_data and supports_tree_reference are set
470
180
on a per instance basis, and are not set (and should not be) at
473
:ivar _custom_format: If set, a specific concrete repository format that
474
will be used when initializing a repository with this
475
RemoteRepositoryFormat.
476
:ivar _creating_repo: If set, the repository object that this
477
RemoteRepositoryFormat was created for: it can be called into
478
to obtain data like the network name.
481
_matchingbzrdir = RemoteBzrDirFormat()
484
repository.RepositoryFormat.__init__(self)
485
self._custom_format = None
486
self._network_name = None
487
self._creating_bzrdir = None
488
self._supports_chks = None
489
self._supports_external_lookups = None
490
self._supports_tree_reference = None
491
self._rich_root_data = None
494
return "%s(_network_name=%r)" % (self.__class__.__name__,
498
def fast_deltas(self):
500
return self._custom_format.fast_deltas
503
def rich_root_data(self):
504
if self._rich_root_data is None:
506
self._rich_root_data = self._custom_format.rich_root_data
507
return self._rich_root_data
510
def supports_chks(self):
511
if self._supports_chks is None:
513
self._supports_chks = self._custom_format.supports_chks
514
return self._supports_chks
517
def supports_external_lookups(self):
518
if self._supports_external_lookups is None:
520
self._supports_external_lookups = \
521
self._custom_format.supports_external_lookups
522
return self._supports_external_lookups
525
def supports_tree_reference(self):
526
if self._supports_tree_reference is None:
528
self._supports_tree_reference = \
529
self._custom_format.supports_tree_reference
530
return self._supports_tree_reference
532
def _vfs_initialize(self, a_bzrdir, shared):
533
"""Helper for common code in initialize."""
534
if self._custom_format:
535
# Custom format requested
536
result = self._custom_format.initialize(a_bzrdir, shared=shared)
537
elif self._creating_bzrdir is not None:
538
# Use the format that the repository we were created to back
540
prior_repo = self._creating_bzrdir.open_repository()
541
prior_repo._ensure_real()
542
result = prior_repo._real_repository._format.initialize(
543
a_bzrdir, shared=shared)
545
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
546
# support remote initialization.
547
# We delegate to a real object at this point (as RemoteBzrDir
548
# delegate to the repository format which would lead to infinite
549
# recursion if we just called a_bzrdir.create_repository.
550
a_bzrdir._ensure_real()
551
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
552
if not isinstance(result, RemoteRepository):
553
return self.open(a_bzrdir)
184
_matchingbzrdir = RemoteBzrDirFormat
557
186
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
187
assert isinstance(a_bzrdir, RemoteBzrDir), \
188
'%r is not a RemoteBzrDir' % (a_bzrdir,)
189
return a_bzrdir.create_repository(shared=shared)
597
191
def open(self, a_bzrdir):
598
if not isinstance(a_bzrdir, RemoteBzrDir):
599
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
192
assert isinstance(a_bzrdir, RemoteBzrDir)
600
193
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
195
def get_format_description(self):
624
return 'Remote: ' + self._custom_format.get_format_description()
196
return 'bzr remote repository'
626
198
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):
199
return self.__class__ == other.__class__
201
def check_conversion_target(self, target_format):
202
if self.rich_root_data and not target_format.rich_root_data:
203
raise errors.BadConversionTarget(
204
'Does not support rich root data.', target_format)
205
if (self.supports_tree_reference and
206
not getattr(target_format, 'supports_tree_reference', False)):
207
raise errors.BadConversionTarget(
208
'Does not support nested trees', target_format)
211
class RemoteRepository(object):
648
212
"""Repository accessed over rpc.
650
214
For the moment most operations are performed using local transport-backed
676
240
self._lock_token = None
677
241
self._lock_count = 0
678
242
self._leave_lock = False
679
# Cache of revision parents; misses are cached during read locks, and
680
# write locks when no _real_repository has been set.
681
self._unstacked_provider = graph.CachingParentsProvider(
682
get_parent_map=self._get_parent_map_rpc)
683
self._unstacked_provider.disable_cache()
685
# These depend on the actual remote format, so force them off for
686
# maximum compatibility. XXX: In future these should depend on the
687
# remote repository instance, but this is irrelevant until we perform
688
# reconcile via an RPC call.
689
self._reconcile_does_inventory_gc = False
690
self._reconcile_fixes_text_parents = False
691
self._reconcile_backsup_inventory = False
692
self.base = self.bzrdir.transport.base
693
# Additional places to query for data.
694
self._fallback_repositories = []
697
def user_transport(self):
698
return self.bzrdir.user_transport
701
def control_transport(self):
702
# XXX: Normally you shouldn't directly get at the remote repository
703
# transport, but I'm not sure it's worth making this method
704
# optional -- mbp 2010-04-21
705
return self.bzrdir.get_repository_transport(None)
708
return "%s(%s)" % (self.__class__.__name__, self.base)
712
def abort_write_group(self, suppress_errors=False):
713
"""Complete a write group on the decorated repository.
715
Smart methods perform operations in a single step so this API
716
is not really applicable except as a compatibility thunk
717
for older plugins that don't use e.g. the CommitBuilder
720
:param suppress_errors: see Repository.abort_write_group.
723
return self._real_repository.abort_write_group(
724
suppress_errors=suppress_errors)
728
"""Decorate the real repository for now.
730
In the long term a full blown network facility is needed to avoid
731
creating a real repository object locally.
734
return self._real_repository.chk_bytes
736
def commit_write_group(self):
737
"""Complete a write group on the decorated repository.
739
Smart methods perform operations in a single step so this API
740
is not really applicable except as a compatibility thunk
741
for older plugins that don't use e.g. the CommitBuilder
745
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
244
def _ensure_real(self):
792
245
"""Ensure that there is a _real_repository set.
794
247
Used before calls to self._real_repository.
796
Note that _ensure_real causes many roundtrips to the server which are
797
not desirable, and prevents the use of smart one-roundtrip RPC's to
798
perform complex operations (such as accessing parent data, streaming
799
revisions etc). Adding calls to _ensure_real should only be done when
800
bringing up new functionality, adding fallbacks for smart methods that
801
require a fallback path, and never to replace an existing smart method
802
invocation. If in doubt chat to the bzr network team.
804
if self._real_repository is None:
805
if 'hpssvfs' in debug.debug_flags:
807
warning('VFS Repository access triggered\n%s',
808
''.join(traceback.format_stack()))
809
self._unstacked_provider.missing_keys.clear()
249
if not self._real_repository:
810
250
self.bzrdir._ensure_real()
811
self._set_real_repository(
812
self.bzrdir._real_bzrdir.open_repository())
814
def _translate_error(self, err, **context):
815
self.bzrdir._translate_error(err, repository=self, **context)
817
def find_text_key_references(self):
818
"""Find the text key references within the repository.
820
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
821
revision_ids. Each altered file-ids has the exact revision_ids that
822
altered it listed explicitly.
823
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
824
to whether they were referred to by the inventory of the
825
revision_id that they contain. The inventory texts from all present
826
revision ids are assessed to generate this report.
829
return self._real_repository.find_text_key_references()
831
def _generate_text_key_index(self):
832
"""Generate a new text key index for the repository.
834
This is an expensive function that will take considerable time to run.
836
:return: A dict mapping (file_id, revision_id) tuples to a list of
837
parents, also (file_id, revision_id) tuples.
840
return self._real_repository._generate_text_key_index()
842
def _get_revision_graph(self, revision_id):
843
"""Private method for using with old (< 1.2) servers to fallback."""
251
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
252
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
254
def get_revision_graph(self, revision_id=None):
255
"""See Repository.get_revision_graph()."""
844
256
if revision_id is None:
846
elif revision.is_null(revision_id):
258
elif revision_id == NULL_REVISION:
849
261
path = self.bzrdir._path_for_remote_call(self._client)
850
response = self._call_expecting_body(
262
assert type(revision_id) is str
263
response = self._client.call_expecting_body(
851
264
'Repository.get_revision_graph', path, revision_id)
852
response_tuple, response_handler = response
853
if response_tuple[0] != 'ok':
854
raise errors.UnexpectedSmartServerResponse(response_tuple)
855
coded = response_handler.read_body_bytes()
857
# no revisions in this repository!
859
lines = coded.split('\n')
862
d = tuple(line.split())
863
revision_graph[d[0]] = d[1:]
865
return revision_graph
868
"""See Repository._get_sink()."""
869
return RemoteStreamSink(self)
871
def _get_source(self, to_format):
872
"""Return a source for streaming from this repository."""
873
return RemoteStreamSource(self, to_format)
265
if response[0][0] not in ['ok', 'nosuchrevision']:
266
raise errors.UnexpectedSmartServerResponse(response[0])
267
if response[0][0] == 'ok':
268
coded = response[1].read_body_bytes()
270
# no revisions in this repository!
272
lines = coded.split('\n')
275
d = list(line.split())
276
revision_graph[d[0]] = d[1:]
278
return revision_graph
280
response_body = response[1].read_body_bytes()
281
assert response_body == ''
282
raise NoSuchRevision(self, revision_id)
876
284
def has_revision(self, revision_id):
877
"""True if this repository has a copy of the revision."""
878
# Copy of bzrlib.repository.Repository.has_revision
879
return revision_id in self.has_revisions((revision_id,))
882
def has_revisions(self, revision_ids):
883
"""Probe to find out the presence of multiple revisions.
885
:param revision_ids: An iterable of revision_ids.
886
:return: A set of the revision_ids that were present.
888
# Copy of bzrlib.repository.Repository.has_revisions
889
parent_map = self.get_parent_map(revision_ids)
890
result = set(parent_map)
891
if _mod_revision.NULL_REVISION in revision_ids:
892
result.add(_mod_revision.NULL_REVISION)
895
def _has_same_fallbacks(self, other_repo):
896
"""Returns true if the repositories have the same fallbacks."""
897
# XXX: copied from Repository; it should be unified into a base class
898
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
899
my_fb = self._fallback_repositories
900
other_fb = other_repo._fallback_repositories
901
if len(my_fb) != len(other_fb):
903
for f, g in zip(my_fb, other_fb):
904
if not f.has_same_location(g):
908
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
913
self.bzrdir.transport.base == other.bzrdir.transport.base)
915
def get_graph(self, other_repository=None):
916
"""Return the graph for this repository format"""
917
parents_provider = self._make_parents_provider(other_repository)
918
return graph.Graph(parents_provider)
921
def get_known_graph_ancestry(self, revision_ids):
922
"""Return the known graph for a set of revision ids and their ancestors.
924
st = static_tuple.StaticTuple
925
revision_keys = [st(r_id).intern() for r_id in revision_ids]
926
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
927
return graph.GraphThunkIdsToKeys(known_graph)
285
"""See Repository.has_revision()."""
286
if revision_id is None:
287
# The null revision is always present.
289
path = self.bzrdir._path_for_remote_call(self._client)
290
response = self._client.call('Repository.has_revision', path, revision_id)
291
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
292
return response[0] == 'yes'
929
294
def gather_stats(self, revid=None, committers=None):
930
295
"""See Repository.gather_stats()."""
931
296
path = self.bzrdir._path_for_remote_call(self._client)
932
# revid can be None to indicate no revisions, not just NULL_REVISION
933
if revid is None or revision.is_null(revid):
297
if revid in (None, NULL_REVISION):
936
300
fmt_revid = revid
1301
507
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1303
509
def make_working_trees(self):
1304
"""See Repository.make_working_trees"""
1306
return self._real_repository.make_working_trees()
1308
def refresh_data(self):
1309
"""Re-read any data needed to to synchronise with disk.
1311
This method is intended to be called after another repository instance
1312
(such as one used by a smart server) has inserted data into the
1313
repository. It may not be called during a write group, but may be
1314
called at any other time.
1316
if self.is_in_write_group():
1317
raise errors.InternalBzrError(
1318
"May not refresh_data while in a write group.")
1319
if self._real_repository is not None:
1320
self._real_repository.refresh_data()
1322
def revision_ids_to_search_result(self, result_set):
1323
"""Convert a set of revision ids to a graph SearchResult."""
1324
result_parents = set()
1325
for parents in self.get_graph().get_parent_map(
1326
result_set).itervalues():
1327
result_parents.update(parents)
1328
included_keys = result_set.intersection(result_parents)
1329
start_keys = result_set.difference(included_keys)
1330
exclude_keys = result_parents.difference(result_set)
1331
result = graph.SearchResult(start_keys, exclude_keys,
1332
len(result_set), result_set)
1336
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1337
"""Return the revision ids that other has that this does not.
1339
These are returned in topological order.
1341
revision_id: only return revision ids included by revision_id.
1343
return repository.InterRepository.get(
1344
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1346
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1348
# No base implementation to use as RemoteRepository is not a subclass
1349
# of Repository; so this is a copy of Repository.fetch().
1350
if fetch_spec is not None and revision_id is not None:
1351
raise AssertionError(
1352
"fetch_spec and revision_id are mutually exclusive.")
1353
if self.is_in_write_group():
1354
raise errors.InternalBzrError(
1355
"May not fetch while in a write group.")
1356
# fast path same-url fetch operations
1357
if (self.has_same_location(source)
1358
and fetch_spec is None
1359
and self._has_same_fallbacks(source)):
1360
# check that last_revision is in 'from' and then return a
1362
if (revision_id is not None and
1363
not revision.is_null(revision_id)):
1364
self.get_revision(revision_id)
1366
# if there is no specific appropriate InterRepository, this will get
1367
# the InterRepository base class, which raises an
1368
# IncompatibleRepositories when asked to fetch.
1369
inter = repository.InterRepository.get(source, self)
1370
return inter.fetch(revision_id=revision_id, pb=pb,
1371
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1373
def create_bundle(self, target, base, fileobj, format=None):
1375
self._real_repository.create_bundle(target, base, fileobj, format)
1378
def get_ancestry(self, revision_id, topo_sorted=True):
1380
return self._real_repository.get_ancestry(revision_id, topo_sorted)
510
"""RemoteRepositories never create working trees by default."""
513
def fetch(self, source, revision_id=None, pb=None):
515
return self._real_repository.fetch(
516
source, revision_id=revision_id, pb=pb)
519
def control_weaves(self):
521
return self._real_repository.control_weaves
524
def get_ancestry(self, revision_id):
526
return self._real_repository.get_ancestry(revision_id)
529
def get_inventory_weave(self):
531
return self._real_repository.get_inventory_weave()
1382
533
def fileids_altered_by_revision_ids(self, revision_ids):
1383
534
self._ensure_real()
1384
535
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1386
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1388
return self._real_repository._get_versioned_file_checker(
1389
revisions, revision_versions_cache)
1391
def iter_files_bytes(self, desired_files):
1392
"""See Repository.iter_file_bytes.
1395
return self._real_repository.iter_files_bytes(desired_files)
1397
def get_parent_map(self, revision_ids):
1398
"""See bzrlib.Graph.get_parent_map()."""
1399
return self._make_parents_provider().get_parent_map(revision_ids)
1401
def _get_parent_map_rpc(self, keys):
1402
"""Helper for get_parent_map that performs the RPC."""
1403
medium = self._client._medium
1404
if medium._is_remote_before((1, 2)):
1405
# We already found out that the server can't understand
1406
# Repository.get_parent_map requests, so just fetch the whole
1409
# Note that this reads the whole graph, when only some keys are
1410
# wanted. On this old server there's no way (?) to get them all
1411
# in one go, and the user probably will have seen a warning about
1412
# the server being old anyhow.
1413
rg = self._get_revision_graph(None)
1414
# There is an API discrepancy between get_parent_map and
1415
# get_revision_graph. Specifically, a "key:()" pair in
1416
# get_revision_graph just means a node has no parents. For
1417
# "get_parent_map" it means the node is a ghost. So fix up the
1418
# graph to correct this.
1419
# https://bugs.launchpad.net/bzr/+bug/214894
1420
# There is one other "bug" which is that ghosts in
1421
# get_revision_graph() are not returned at all. But we won't worry
1422
# about that for now.
1423
for node_id, parent_ids in rg.iteritems():
1424
if parent_ids == ():
1425
rg[node_id] = (NULL_REVISION,)
1426
rg[NULL_REVISION] = ()
1431
raise ValueError('get_parent_map(None) is not valid')
1432
if NULL_REVISION in keys:
1433
keys.discard(NULL_REVISION)
1434
found_parents = {NULL_REVISION:()}
1436
return found_parents
1439
# TODO(Needs analysis): We could assume that the keys being requested
1440
# from get_parent_map are in a breadth first search, so typically they
1441
# will all be depth N from some common parent, and we don't have to
1442
# have the server iterate from the root parent, but rather from the
1443
# keys we're searching; and just tell the server the keyspace we
1444
# already have; but this may be more traffic again.
1446
# Transform self._parents_map into a search request recipe.
1447
# TODO: Manage this incrementally to avoid covering the same path
1448
# repeatedly. (The server will have to on each request, but the less
1449
# work done the better).
1451
# Negative caching notes:
1452
# new server sends missing when a request including the revid
1453
# 'include-missing:' is present in the request.
1454
# missing keys are serialised as missing:X, and we then call
1455
# provider.note_missing(X) for-all X
1456
parents_map = self._unstacked_provider.get_cached_map()
1457
if parents_map is None:
1458
# Repository is not locked, so there's no cache.
1460
# start_set is all the keys in the cache
1461
start_set = set(parents_map)
1462
# result set is all the references to keys in the cache
1463
result_parents = set()
1464
for parents in parents_map.itervalues():
1465
result_parents.update(parents)
1466
stop_keys = result_parents.difference(start_set)
1467
# We don't need to send ghosts back to the server as a position to
1469
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1470
key_count = len(parents_map)
1471
if (NULL_REVISION in result_parents
1472
and NULL_REVISION in self._unstacked_provider.missing_keys):
1473
# If we pruned NULL_REVISION from the stop_keys because it's also
1474
# in our cache of "missing" keys we need to increment our key count
1475
# by 1, because the reconsitituted SearchResult on the server will
1476
# still consider NULL_REVISION to be an included key.
1478
included_keys = start_set.intersection(result_parents)
1479
start_set.difference_update(included_keys)
1480
recipe = ('manual', start_set, stop_keys, key_count)
1481
body = self._serialise_search_recipe(recipe)
1482
path = self.bzrdir._path_for_remote_call(self._client)
1484
if type(key) is not str:
1486
"key %r not a plain string" % (key,))
1487
verb = 'Repository.get_parent_map'
1488
args = (path, 'include-missing:') + tuple(keys)
1490
response = self._call_with_body_bytes_expecting_body(
1492
except errors.UnknownSmartMethod:
1493
# Server does not support this method, so get the whole graph.
1494
# Worse, we have to force a disconnection, because the server now
1495
# doesn't realise it has a body on the wire to consume, so the
1496
# only way to recover is to abandon the connection.
1498
'Server is too old for fast get_parent_map, reconnecting. '
1499
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1501
# To avoid having to disconnect repeatedly, we keep track of the
1502
# fact the server doesn't understand remote methods added in 1.2.
1503
medium._remember_remote_is_before((1, 2))
1504
# Recurse just once and we should use the fallback code.
1505
return self._get_parent_map_rpc(keys)
1506
response_tuple, response_handler = response
1507
if response_tuple[0] not in ['ok']:
1508
response_handler.cancel_read_body()
1509
raise errors.UnexpectedSmartServerResponse(response_tuple)
1510
if response_tuple[0] == 'ok':
1511
coded = bz2.decompress(response_handler.read_body_bytes())
1513
# no revisions found
1515
lines = coded.split('\n')
1518
d = tuple(line.split())
1520
revision_graph[d[0]] = d[1:]
1523
if d[0].startswith('missing:'):
1525
self._unstacked_provider.note_missing_key(revid)
1527
# no parents - so give the Graph result
1529
revision_graph[d[0]] = (NULL_REVISION,)
1530
return revision_graph
1532
537
@needs_read_lock
1533
538
def get_signature_text(self, revision_id):
1534
539
self._ensure_real()
1535
540
return self._real_repository.get_signature_text(revision_id)
1537
542
@needs_read_lock
1538
def _get_inventory_xml(self, revision_id):
1540
return self._real_repository._get_inventory_xml(revision_id)
543
def get_revision_graph_with_ghosts(self, revision_ids=None):
545
return self._real_repository.get_revision_graph_with_ghosts(
546
revision_ids=revision_ids)
549
def get_inventory_xml(self, revision_id):
551
return self._real_repository.get_inventory_xml(revision_id)
553
def deserialise_inventory(self, revision_id, xml):
555
return self._real_repository.deserialise_inventory(revision_id, xml)
1542
557
def reconcile(self, other=None, thorough=False):
1543
558
self._ensure_real()
1544
559
return self._real_repository.reconcile(other=other, thorough=thorough)
1546
561
def all_revision_ids(self):
1547
562
self._ensure_real()
1548
563
return self._real_repository.all_revision_ids()
1551
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1553
return self._real_repository.get_deltas_for_revisions(revisions,
1554
specific_fileids=specific_fileids)
1557
def get_revision_delta(self, revision_id, specific_fileids=None):
1559
return self._real_repository.get_revision_delta(revision_id,
1560
specific_fileids=specific_fileids)
566
def get_deltas_for_revisions(self, revisions):
568
return self._real_repository.get_deltas_for_revisions(revisions)
571
def get_revision_delta(self, revision_id):
573
return self._real_repository.get_revision_delta(revision_id)
1562
575
@needs_read_lock
1563
576
def revision_trees(self, revision_ids):
1696
646
def _serializer(self):
1697
return self._format._serializer
648
return self._real_repository._serializer
1699
650
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1700
651
self._ensure_real()
1701
652
return self._real_repository.store_revision_signature(
1702
653
gpg_strategy, plaintext, revision_id)
1704
def add_signature_text(self, revision_id, signature):
1706
return self._real_repository.add_signature_text(revision_id, signature)
1708
655
def has_signature_for_revision_id(self, revision_id):
1709
656
self._ensure_real()
1710
657
return self._real_repository.has_signature_for_revision_id(revision_id)
1712
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1714
return self._real_repository.item_keys_introduced_by(revision_ids,
1715
_files_pb=_files_pb)
1717
def revision_graph_can_have_wrong_parents(self):
1718
# The answer depends on the remote repo format.
1720
return self._real_repository.revision_graph_can_have_wrong_parents()
1722
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1724
return self._real_repository._find_inconsistent_revision_parents(
1727
def _check_for_inconsistent_revision_parents(self):
1729
return self._real_repository._check_for_inconsistent_revision_parents()
1731
def _make_parents_provider(self, other=None):
1732
providers = [self._unstacked_provider]
1733
if other is not None:
1734
providers.insert(0, other)
1735
providers.extend(r._make_parents_provider() for r in
1736
self._fallback_repositories)
1737
return graph.StackedParentsProvider(providers)
1739
def _serialise_search_recipe(self, recipe):
1740
"""Serialise a graph search recipe.
1742
:param recipe: A search recipe (start, stop, count).
1743
:return: Serialised bytes.
1745
start_keys = ' '.join(recipe[1])
1746
stop_keys = ' '.join(recipe[2])
1747
count = str(recipe[3])
1748
return '\n'.join((start_keys, stop_keys, count))
1750
def _serialise_search_result(self, search_result):
1751
if isinstance(search_result, graph.PendingAncestryResult):
1752
parts = ['ancestry-of']
1753
parts.extend(search_result.heads)
1755
recipe = search_result.get_recipe()
1756
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1757
return '\n'.join(parts)
1760
path = self.bzrdir._path_for_remote_call(self._client)
1762
response = self._call('PackRepository.autopack', path)
1763
except errors.UnknownSmartMethod:
1765
self._real_repository._pack_collection.autopack()
1768
if response[0] != 'ok':
1769
raise errors.UnexpectedSmartServerResponse(response)
1772
class RemoteStreamSink(repository.StreamSink):
1774
def _insert_real(self, stream, src_format, resume_tokens):
1775
self.target_repo._ensure_real()
1776
sink = self.target_repo._real_repository._get_sink()
1777
result = sink.insert_stream(stream, src_format, resume_tokens)
1779
self.target_repo.autopack()
1782
def insert_stream(self, stream, src_format, resume_tokens):
1783
target = self.target_repo
1784
target._unstacked_provider.missing_keys.clear()
1785
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1786
if target._lock_token:
1787
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1788
lock_args = (target._lock_token or '',)
1790
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1792
client = target._client
1793
medium = client._medium
1794
path = target.bzrdir._path_for_remote_call(client)
1795
# Probe for the verb to use with an empty stream before sending the
1796
# real stream to it. We do this both to avoid the risk of sending a
1797
# large request that is then rejected, and because we don't want to
1798
# implement a way to buffer, rewind, or restart the stream.
1800
for verb, required_version in candidate_calls:
1801
if medium._is_remote_before(required_version):
1804
# We've already done the probing (and set _is_remote_before) on
1805
# a previous insert.
1808
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1810
response = client.call_with_body_stream(
1811
(verb, path, '') + lock_args, byte_stream)
1812
except errors.UnknownSmartMethod:
1813
medium._remember_remote_is_before(required_version)
1819
return self._insert_real(stream, src_format, resume_tokens)
1820
self._last_inv_record = None
1821
self._last_substream = None
1822
if required_version < (1, 19):
1823
# Remote side doesn't support inventory deltas. Wrap the stream to
1824
# make sure we don't send any. If the stream contains inventory
1825
# deltas we'll interrupt the smart insert_stream request and
1827
stream = self._stop_stream_if_inventory_delta(stream)
1828
byte_stream = smart_repo._stream_to_byte_stream(
1830
resume_tokens = ' '.join(resume_tokens)
1831
response = client.call_with_body_stream(
1832
(verb, path, resume_tokens) + lock_args, byte_stream)
1833
if response[0][0] not in ('ok', 'missing-basis'):
1834
raise errors.UnexpectedSmartServerResponse(response)
1835
if self._last_substream is not None:
1836
# The stream included an inventory-delta record, but the remote
1837
# side isn't new enough to support them. So we need to send the
1838
# rest of the stream via VFS.
1839
self.target_repo.refresh_data()
1840
return self._resume_stream_with_vfs(response, src_format)
1841
if response[0][0] == 'missing-basis':
1842
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1843
resume_tokens = tokens
1844
return resume_tokens, set(missing_keys)
1846
self.target_repo.refresh_data()
1849
def _resume_stream_with_vfs(self, response, src_format):
1850
"""Resume sending a stream via VFS, first resending the record and
1851
substream that couldn't be sent via an insert_stream verb.
1853
if response[0][0] == 'missing-basis':
1854
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1855
# Ignore missing_keys, we haven't finished inserting yet
1858
def resume_substream():
1859
# Yield the substream that was interrupted.
1860
for record in self._last_substream:
1862
self._last_substream = None
1863
def resume_stream():
1864
# Finish sending the interrupted substream
1865
yield ('inventory-deltas', resume_substream())
1866
# Then simply continue sending the rest of the stream.
1867
for substream_kind, substream in self._last_stream:
1868
yield substream_kind, substream
1869
return self._insert_real(resume_stream(), src_format, tokens)
1871
def _stop_stream_if_inventory_delta(self, stream):
1872
"""Normally this just lets the original stream pass-through unchanged.
1874
However if any 'inventory-deltas' substream occurs it will stop
1875
streaming, and store the interrupted substream and stream in
1876
self._last_substream and self._last_stream so that the stream can be
1877
resumed by _resume_stream_with_vfs.
1880
stream_iter = iter(stream)
1881
for substream_kind, substream in stream_iter:
1882
if substream_kind == 'inventory-deltas':
1883
self._last_substream = substream
1884
self._last_stream = stream_iter
1887
yield substream_kind, substream
1890
class RemoteStreamSource(repository.StreamSource):
1891
"""Stream data from a remote server."""
1893
def get_stream(self, search):
1894
if (self.from_repository._fallback_repositories and
1895
self.to_format._fetch_order == 'topological'):
1896
return self._real_stream(self.from_repository, search)
1899
repos = [self.from_repository]
1905
repos.extend(repo._fallback_repositories)
1906
sources.append(repo)
1907
return self.missing_parents_chain(search, sources)
1909
def get_stream_for_missing_keys(self, missing_keys):
1910
self.from_repository._ensure_real()
1911
real_repo = self.from_repository._real_repository
1912
real_source = real_repo._get_source(self.to_format)
1913
return real_source.get_stream_for_missing_keys(missing_keys)
1915
def _real_stream(self, repo, search):
1916
"""Get a stream for search from repo.
1918
This never called RemoteStreamSource.get_stream, and is a heler
1919
for RemoteStreamSource._get_stream to allow getting a stream
1920
reliably whether fallback back because of old servers or trying
1921
to stream from a non-RemoteRepository (which the stacked support
1924
source = repo._get_source(self.to_format)
1925
if isinstance(source, RemoteStreamSource):
1927
source = repo._real_repository._get_source(self.to_format)
1928
return source.get_stream(search)
1930
def _get_stream(self, repo, search):
1931
"""Core worker to get a stream from repo for search.
1933
This is used by both get_stream and the stacking support logic. It
1934
deliberately gets a stream for repo which does not need to be
1935
self.from_repository. In the event that repo is not Remote, or
1936
cannot do a smart stream, a fallback is made to the generic
1937
repository._get_stream() interface, via self._real_stream.
1939
In the event of stacking, streams from _get_stream will not
1940
contain all the data for search - this is normal (see get_stream).
1942
:param repo: A repository.
1943
:param search: A search.
1945
# Fallbacks may be non-smart
1946
if not isinstance(repo, RemoteRepository):
1947
return self._real_stream(repo, search)
1948
client = repo._client
1949
medium = client._medium
1950
path = repo.bzrdir._path_for_remote_call(client)
1951
search_bytes = repo._serialise_search_result(search)
1952
args = (path, self.to_format.network_name())
1954
('Repository.get_stream_1.19', (1, 19)),
1955
('Repository.get_stream', (1, 13))]
1957
for verb, version in candidate_verbs:
1958
if medium._is_remote_before(version):
1961
response = repo._call_with_body_bytes_expecting_body(
1962
verb, args, search_bytes)
1963
except errors.UnknownSmartMethod:
1964
medium._remember_remote_is_before(version)
1966
response_tuple, response_handler = response
1970
return self._real_stream(repo, search)
1971
if response_tuple[0] != 'ok':
1972
raise errors.UnexpectedSmartServerResponse(response_tuple)
1973
byte_stream = response_handler.read_streamed_body()
1974
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1975
if src_format.network_name() != repo._format.network_name():
1976
raise AssertionError(
1977
"Mismatched RemoteRepository and stream src %r, %r" % (
1978
src_format.network_name(), repo._format.network_name()))
1981
def missing_parents_chain(self, search, sources):
1982
"""Chain multiple streams together to handle stacking.
1984
:param search: The overall search to satisfy with streams.
1985
:param sources: A list of Repository objects to query.
1987
self.from_serialiser = self.from_repository._format._serializer
1988
self.seen_revs = set()
1989
self.referenced_revs = set()
1990
# If there are heads in the search, or the key count is > 0, we are not
1992
while not search.is_empty() and len(sources) > 1:
1993
source = sources.pop(0)
1994
stream = self._get_stream(source, search)
1995
for kind, substream in stream:
1996
if kind != 'revisions':
1997
yield kind, substream
1999
yield kind, self.missing_parents_rev_handler(substream)
2000
search = search.refine(self.seen_revs, self.referenced_revs)
2001
self.seen_revs = set()
2002
self.referenced_revs = set()
2003
if not search.is_empty():
2004
for kind, stream in self._get_stream(sources[0], search):
2007
def missing_parents_rev_handler(self, substream):
2008
for content in substream:
2009
revision_bytes = content.get_bytes_as('fulltext')
2010
revision = self.from_serialiser.read_revision_from_string(
2012
self.seen_revs.add(content.key[-1])
2013
self.referenced_revs.update(revision.parent_ids)
2017
660
class RemoteBranchLockableFiles(LockableFiles):
2018
661
"""A 'LockableFiles' implementation that talks to a smart server.
2020
663
This is not a public interface class.
2033
676
self._dir_mode = None
2034
677
self._file_mode = None
680
"""'get' a remote path as per the LockableFiles interface.
682
:param path: the file to 'get'. If this is 'branch.conf', we do not
683
just retrieve a file, instead we ask the smart server to generate
684
a configuration for us - which is retrieved as an INI file.
686
if path == 'branch.conf':
687
path = self.bzrdir._path_for_remote_call(self._client)
688
response = self._client.call_expecting_body(
689
'Branch.get_config_file', path)
690
assert response[0][0] == 'ok', \
691
'unexpected response code %s' % (response[0],)
692
return StringIO(response[1].read_body_bytes())
695
return LockableFiles.get(self, path)
2037
698
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
700
def __eq__(self, other):
2047
return (isinstance(other, RemoteBranchFormat) and
701
return (isinstance(other, RemoteBranchFormat) and
2048
702
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
704
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)
2137
def supports_tags(self):
2138
# Remote branches might support tags, but we won't know until we
2139
# 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):
705
return 'Remote BZR Branch'
707
def get_format_string(self):
708
return 'Remote BZR Branch'
710
def open(self, a_bzrdir):
711
assert isinstance(a_bzrdir, RemoteBzrDir)
712
return a_bzrdir.open_branch()
714
def initialize(self, a_bzrdir):
715
assert isinstance(a_bzrdir, RemoteBzrDir)
716
return a_bzrdir.create_branch()
719
class RemoteBranch(branch.Branch):
2153
720
"""Branch stored on a server accessed by HPSS RPC.
2155
722
At the moment most operations are mapped down to simple file operations.
2158
725
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2159
_client=None, format=None, setup_stacking=True, name=None):
2160
727
"""Create a RemoteBranch instance.
2162
729
:param real_branch: An optional local implementation of the branch
2163
730
format, usually accessing the data via the VFS.
2164
731
: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
733
# We intentionally don't call the parent class's __init__, because it
2174
734
# will try to assign to self.tags, which is a property in this subclass.
2175
735
# And the parent's __init__ doesn't do much anyway.
736
self._revision_history_cache = None
2176
737
self.bzrdir = remote_bzrdir
2177
738
if _client is not None:
2178
739
self._client = _client
2180
self._client = remote_bzrdir._client
741
self._client = client._SmartClient(self.bzrdir._medium)
2181
742
self.repository = remote_repository
2182
743
if real_branch is not None:
2183
744
self._real_branch = real_branch
2191
752
self._real_branch.repository = self.repository
2193
754
self._real_branch = None
2194
# Fill out expected attributes of branch for bzrlib API users.
2195
self._clear_cached_state()
2196
# TODO: deprecate self.base in favor of user_url
2197
self.base = self.bzrdir.user_url
755
# Fill out expected attributes of branch for bzrlib api users.
756
self._format = RemoteBranchFormat()
757
self.base = self.bzrdir.root_transport.base
2199
758
self._control_files = None
2200
759
self._lock_mode = None
2201
760
self._lock_token = None
2202
self._repo_lock_token = None
2203
761
self._lock_count = 0
2204
762
self._leave_lock = False
2205
# Setup a format: note that we cannot call _ensure_real until all the
2206
# attributes above are set: This code cannot be moved higher up in this
2209
self._format = RemoteBranchFormat()
2210
if real_branch is not None:
2211
self._format._network_name = \
2212
self._real_branch._format.network_name()
2214
self._format = format
2215
# when we do _ensure_real we may need to pass ignore_fallbacks to the
2216
# branch.open_branch method.
2217
self._real_ignore_fallbacks = not setup_stacking
2218
if not self._format._network_name:
2219
# Did not get from open_branchV2 - old server.
2221
self._format._network_name = \
2222
self._real_branch._format.network_name()
2223
self.tags = self._format.make_tags(self)
2224
# The base class init is not called, so we duplicate this:
2225
hooks = branch.Branch.hooks['open']
2228
self._is_stacked = False
2230
self._setup_stacking()
2232
def _setup_stacking(self):
2233
# configure stacking into the remote repository, by reading it from
2236
fallback_url = self.get_stacked_on_url()
2237
except (errors.NotStacked, errors.UnstackableBranchFormat,
2238
errors.UnstackableRepositoryFormat), e:
2240
self._is_stacked = True
2241
self._activate_fallback_location(fallback_url)
2243
def _get_config(self):
2244
return RemoteBranchConfig(self)
2246
def _get_real_transport(self):
2247
# if we try vfs access, return the real branch's vfs transport
2249
return self._real_branch._transport
2251
_transport = property(_get_real_transport)
2254
return "%s(%s)" % (self.__class__.__name__, self.base)
2258
764
def _ensure_real(self):
2259
765
"""Ensure that there is a _real_branch set.
2261
767
Used before calls to self._real_branch.
2263
if self._real_branch is None:
2264
if not vfs.vfs_enabled():
2265
raise AssertionError('smart server vfs must be enabled '
2266
'to use vfs implementation')
769
if not self._real_branch:
770
assert vfs.vfs_enabled()
2267
771
self.bzrdir._ensure_real()
2268
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2269
ignore_fallbacks=self._real_ignore_fallbacks, name=self._name)
2270
if self.repository._real_repository is None:
2271
# Give the remote repository the matching real repo.
2272
real_repo = self._real_branch.repository
2273
if isinstance(real_repo, RemoteRepository):
2274
real_repo._ensure_real()
2275
real_repo = real_repo._real_repository
2276
self.repository._set_real_repository(real_repo)
2277
# Give the real branch the remote repository to let fast-pathing
772
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
773
# Give the remote repository the matching real repo.
774
real_repo = self._real_branch.repository
775
if isinstance(real_repo, RemoteRepository):
776
real_repo._ensure_real()
777
real_repo = real_repo._real_repository
778
self.repository._set_real_repository(real_repo)
779
# Give the branch the remote repository to let fast-pathing happen.
2279
780
self._real_branch.repository = self.repository
781
# XXX: deal with _lock_mode == 'w'
2280
782
if self._lock_mode == 'r':
2281
783
self._real_branch.lock_read()
2282
elif self._lock_mode == 'w':
2283
self._real_branch.lock_write(token=self._lock_token)
2285
def _translate_error(self, err, **context):
2286
self.repository._translate_error(err, branch=self, **context)
2288
def _clear_cached_state(self):
2289
super(RemoteBranch, self)._clear_cached_state()
2290
if self._real_branch is not None:
2291
self._real_branch._clear_cached_state()
2293
def _clear_cached_state_of_remote_branch_only(self):
2294
"""Like _clear_cached_state, but doesn't clear the cache of
2297
This is useful when falling back to calling a method of
2298
self._real_branch that changes state. In that case the underlying
2299
branch changes, so we need to invalidate this RemoteBranch's cache of
2300
it. However, there's no need to invalidate the _real_branch's cache
2301
too, in fact doing so might harm performance.
2303
super(RemoteBranch, self)._clear_cached_state()
2306
786
def control_files(self):
2437
865
raise errors.ReadOnlyTransaction
2439
867
if token is not None:
2440
# A token was given to lock_write, and we're relocking, so
2441
# check that the given token actually matches the one we
868
# A token was given to lock_write, and we're relocking, so check
869
# that the given token actually matches the one we already have.
2443
870
if token != self._lock_token:
2444
871
raise errors.TokenMismatch(token, self._lock_token)
2445
872
self._lock_count += 1
2446
# Re-lock the repository too.
2447
self.repository.lock_write(self._repo_lock_token)
2448
return self._lock_token or None
873
return self._lock_token
2450
875
def _unlock(self, branch_token, repo_token):
2451
err_context = {'token': str((branch_token, repo_token))}
2452
response = self._call(
2453
'Branch.unlock', self._remote_path(), branch_token,
2454
repo_token or '', **err_context)
876
path = self.bzrdir._path_for_remote_call(self._client)
877
response = self._client.call('Branch.unlock', path, branch_token,
2455
879
if response == ('ok',):
2457
raise errors.UnexpectedSmartServerResponse(response)
881
elif response[0] == 'TokenMismatch':
882
raise errors.TokenMismatch(
883
str((branch_token, repo_token)), '(remote tokens)')
885
assert False, 'unexpected response code %s' % (response,)
2459
@only_raises(errors.LockNotHeld, errors.LockBroken)
2460
887
def unlock(self):
2462
self._lock_count -= 1
2463
if not self._lock_count:
2464
self._clear_cached_state()
2465
mode = self._lock_mode
2466
self._lock_mode = None
2467
if self._real_branch is not None:
2468
if (not self._leave_lock and mode == 'w' and
2469
self._repo_lock_token):
2470
# If this RemoteBranch will remove the physical lock
2471
# for the repository, make sure the _real_branch
2472
# doesn't do it first. (Because the _real_branch's
2473
# repository is set to be the RemoteRepository.)
2474
self._real_branch.repository.leave_lock_in_place()
2475
self._real_branch.unlock()
2477
# Only write-locked branched need to make a remote method
2478
# call to perform the unlock.
2480
if not self._lock_token:
2481
raise AssertionError('Locked, but no token!')
2482
branch_token = self._lock_token
2483
repo_token = self._repo_lock_token
2484
self._lock_token = None
2485
self._repo_lock_token = None
888
self._lock_count -= 1
889
if not self._lock_count:
890
self._clear_cached_state()
891
mode = self._lock_mode
892
self._lock_mode = None
893
if self._real_branch is not None:
2486
894
if not self._leave_lock:
2487
self._unlock(branch_token, repo_token)
2489
self.repository.unlock()
895
# If this RemoteBranch will remove the physical lock for the
896
# repository, make sure the _real_branch doesn't do it
897
# first. (Because the _real_branch's repository is set to
898
# be the RemoteRepository.)
899
self._real_branch.repository.leave_lock_in_place()
900
self._real_branch.unlock()
902
# Only write-locked branched need to make a remote method call
903
# to perfom the unlock.
905
assert self._lock_token, 'Locked, but no token!'
906
branch_token = self._lock_token
907
repo_token = self._repo_lock_token
908
self._lock_token = None
909
self._repo_lock_token = None
910
if not self._leave_lock:
911
self._unlock(branch_token, repo_token)
2491
913
def break_lock(self):
2492
914
self._ensure_real()
2493
915
return self._real_branch.break_lock()
2495
917
def leave_lock_in_place(self):
2496
if not self._lock_token:
2497
raise NotImplementedError(self.leave_lock_in_place)
2498
918
self._leave_lock = True
2500
920
def dont_leave_lock_in_place(self):
2501
if not self._lock_token:
2502
raise NotImplementedError(self.dont_leave_lock_in_place)
2503
921
self._leave_lock = False
2506
def get_rev_id(self, revno, history=None):
2508
return _mod_revision.NULL_REVISION
2509
last_revision_info = self.last_revision_info()
2510
ok, result = self.repository.get_rev_id_for_revno(
2511
revno, last_revision_info)
2514
missing_parent = result[1]
2515
# Either the revision named by the server is missing, or its parent
2516
# is. Call get_parent_map to determine which, so that we report a
2518
parent_map = self.repository.get_parent_map([missing_parent])
2519
if missing_parent in parent_map:
2520
missing_parent = parent_map[missing_parent]
2521
raise errors.RevisionNotPresent(missing_parent, self.repository)
2523
def _last_revision_info(self):
2524
response = self._call('Branch.last_revision_info', self._remote_path())
2525
if response[0] != 'ok':
2526
raise SmartProtocolError('unexpected response code %s' % (response,))
923
def last_revision_info(self):
924
"""See Branch.last_revision_info()."""
925
path = self.bzrdir._path_for_remote_call(self._client)
926
response = self._client.call('Branch.last_revision_info', path)
927
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
2527
928
revno = int(response[1])
2528
929
last_revision = response[2]
2529
930
return (revno, last_revision)
2531
932
def _gen_revision_history(self):
2532
933
"""See Branch._gen_revision_history()."""
2533
if self._is_stacked:
2535
return self._real_branch._gen_revision_history()
2536
response_tuple, response_handler = self._call_expecting_body(
2537
'Branch.revision_history', self._remote_path())
2538
if response_tuple[0] != 'ok':
2539
raise errors.UnexpectedSmartServerResponse(response_tuple)
2540
result = response_handler.read_body_bytes().split('\x00')
934
path = self.bzrdir._path_for_remote_call(self._client)
935
response = self._client.call_expecting_body(
936
'Branch.revision_history', path)
937
assert response[0][0] == 'ok', ('unexpected response code %s'
939
result = response[1].read_body_bytes().split('\x00')
2541
940
if result == ['']:
2545
def _remote_path(self):
2546
return self.bzrdir._path_for_remote_call(self._client)
2548
def _set_last_revision_descendant(self, revision_id, other_branch,
2549
allow_diverged=False, allow_overwrite_descendant=False):
2550
# This performs additional work to meet the hook contract; while its
2551
# undesirable, we have to synthesise the revno to call the hook, and
2552
# not calling the hook is worse as it means changes can't be prevented.
2553
# Having calculated this though, we can't just call into
2554
# set_last_revision_info as a simple call, because there is a set_rh
2555
# hook that some folk may still be using.
2556
old_revno, old_revid = self.last_revision_info()
2557
history = self._lefthand_history(revision_id)
2558
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2559
err_context = {'other_branch': other_branch}
2560
response = self._call('Branch.set_last_revision_ex',
2561
self._remote_path(), self._lock_token, self._repo_lock_token,
2562
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2564
self._clear_cached_state()
2565
if len(response) != 3 and response[0] != 'ok':
2566
raise errors.UnexpectedSmartServerResponse(response)
2567
new_revno, new_revision_id = response[1:]
2568
self._last_revision_info_cache = new_revno, new_revision_id
2569
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2570
if self._real_branch is not None:
2571
cache = new_revno, new_revision_id
2572
self._real_branch._last_revision_info_cache = cache
2574
def _set_last_revision(self, revision_id):
2575
old_revno, old_revid = self.last_revision_info()
2576
# This performs additional work to meet the hook contract; while its
2577
# undesirable, we have to synthesise the revno to call the hook, and
2578
# not calling the hook is worse as it means changes can't be prevented.
2579
# Having calculated this though, we can't just call into
2580
# set_last_revision_info as a simple call, because there is a set_rh
2581
# hook that some folk may still be using.
2582
history = self._lefthand_history(revision_id)
2583
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2584
self._clear_cached_state()
2585
response = self._call('Branch.set_last_revision',
2586
self._remote_path(), self._lock_token, self._repo_lock_token,
2588
if response != ('ok',):
2589
raise errors.UnexpectedSmartServerResponse(response)
2590
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2592
944
@needs_write_lock
2593
945
def set_revision_history(self, rev_history):
2594
946
# Send just the tip revision of the history; the server will generate
2595
947
# the full history from that. If the revision doesn't exist in this
2596
948
# branch, NoSuchRevision will be raised.
949
path = self.bzrdir._path_for_remote_call(self._client)
2597
950
if rev_history == []:
2598
951
rev_id = 'null:'
2600
953
rev_id = rev_history[-1]
2601
self._set_last_revision(rev_id)
2602
for hook in branch.Branch.hooks['set_rh']:
2603
hook(self, rev_history)
954
response = self._client.call('Branch.set_last_revision',
955
path, self._lock_token, self._repo_lock_token, rev_id)
956
if response[0] == 'NoSuchRevision':
957
raise NoSuchRevision(self, rev_id)
959
assert response == ('ok',), (
960
'unexpected response code %r' % (response,))
2604
961
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)
2648
def pull(self, source, overwrite=False, stop_revision=None,
2650
self._clear_cached_state_of_remote_branch_only()
2652
return self._real_branch.pull(
2653
source, overwrite=overwrite, stop_revision=stop_revision,
2654
_override_hook_target=self, **kwargs)
963
def get_parent(self):
965
return self._real_branch.get_parent()
967
def set_parent(self, url):
969
return self._real_branch.set_parent(url)
971
def get_config(self):
972
return RemoteBranchConfig(self)
974
def sprout(self, to_bzrdir, revision_id=None):
975
# Like Branch.sprout, except that it sprouts a branch in the default
976
# format, because RemoteBranches can't be created at arbitrary URLs.
977
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
978
# to_bzrdir.create_branch...
979
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
980
self.copy_content_into(result, revision_id=revision_id)
981
result.set_parent(self.bzrdir.root_transport.base)
985
def append_revision(self, *revision_ids):
987
return self._real_branch.append_revision(*revision_ids)
990
def pull(self, source, overwrite=False, stop_revision=None):
992
self._real_branch.pull(
993
source, overwrite=overwrite, stop_revision=stop_revision)
2656
995
@needs_read_lock
2657
996
def push(self, target, overwrite=False, stop_revision=None):
2658
997
self._ensure_real()
2659
998
return self._real_branch.push(
2660
target, overwrite=overwrite, stop_revision=stop_revision,
2661
_override_hook_source_branch=self)
999
target, overwrite=overwrite, stop_revision=stop_revision)
2663
1001
def is_locked(self):
2664
1002
return self._lock_count >= 1
2667
def revision_id_to_revno(self, revision_id):
2669
return self._real_branch.revision_id_to_revno(revision_id)
2672
1004
def set_last_revision_info(self, revno, revision_id):
2673
# XXX: These should be returned by the set_last_revision_info verb
2674
old_revno, old_revid = self.last_revision_info()
2675
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2676
revision_id = ensure_null(revision_id)
2678
response = self._call('Branch.set_last_revision_info',
2679
self._remote_path(), self._lock_token, self._repo_lock_token,
2680
str(revno), revision_id)
2681
except errors.UnknownSmartMethod:
2683
self._clear_cached_state_of_remote_branch_only()
2684
self._real_branch.set_last_revision_info(revno, revision_id)
2685
self._last_revision_info_cache = revno, revision_id
2687
if response == ('ok',):
2688
self._clear_cached_state()
2689
self._last_revision_info_cache = revno, revision_id
2690
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2691
# Update the _real_branch's cache too.
2692
if self._real_branch is not None:
2693
cache = self._last_revision_info_cache
2694
self._real_branch._last_revision_info_cache = cache
2696
raise errors.UnexpectedSmartServerResponse(response)
1006
self._clear_cached_state()
1007
return self._real_branch.set_last_revision_info(revno, revision_id)
2699
1009
def generate_revision_history(self, revision_id, last_rev=None,
2700
1010
other_branch=None):
2701
medium = self._client._medium
2702
if not medium._is_remote_before((1, 6)):
2703
# Use a smart method for 1.6 and above servers
2705
self._set_last_revision_descendant(revision_id, other_branch,
2706
allow_diverged=True, allow_overwrite_descendant=True)
2708
except errors.UnknownSmartMethod:
2709
medium._remember_remote_is_before((1, 6))
2710
self._clear_cached_state_of_remote_branch_only()
2711
self.set_revision_history(self._lefthand_history(revision_id,
2712
last_rev=last_rev,other_branch=other_branch))
1012
return self._real_branch.generate_revision_history(
1013
revision_id, last_rev=last_rev, other_branch=other_branch)
1018
return self._real_branch.tags
2714
1020
def set_push_location(self, location):
2715
1021
self._ensure_real()
2716
1022
return self._real_branch.set_push_location(location)
2719
class RemoteConfig(object):
2720
"""A Config that reads and writes from smart verbs.
2722
It is a low-level object that considers config data to be name/value pairs
2723
that may be associated with a section. Assigning meaning to the these
2724
values is done at higher levels like bzrlib.config.TreeConfig.
2727
def get_option(self, name, section=None, default=None):
2728
"""Return the value associated with a named option.
2730
:param name: The name of the value
2731
:param section: The section the option is in (if any)
2732
:param default: The value to return if the value is not set
2733
:return: The value or default value
2736
configobj = self._get_configobj()
2738
section_obj = configobj
2741
section_obj = configobj[section]
2744
return section_obj.get(name, default)
2745
except errors.UnknownSmartMethod:
2746
return self._vfs_get_option(name, section, default)
2748
def _response_to_configobj(self, response):
2749
if len(response[0]) and response[0][0] != 'ok':
2750
raise errors.UnexpectedSmartServerResponse(response)
2751
lines = response[1].read_body_bytes().splitlines()
2752
return config.ConfigObj(lines, encoding='utf-8')
2755
class RemoteBranchConfig(RemoteConfig):
2756
"""A RemoteConfig for Branches."""
2758
def __init__(self, branch):
2759
self._branch = branch
2761
def _get_configobj(self):
2762
path = self._branch._remote_path()
2763
response = self._branch._client.call_expecting_body(
2764
'Branch.get_config_file', path)
2765
return self._response_to_configobj(response)
2767
def set_option(self, value, name, section=None):
2768
"""Set the value associated with a named option.
2770
:param value: The value to set
2771
:param name: The name of the value to set
2772
:param section: The section the option is in (if any)
2774
medium = self._branch._client._medium
2775
if medium._is_remote_before((1, 14)):
2776
return self._vfs_set_option(value, name, section)
2778
path = self._branch._remote_path()
2779
response = self._branch._client.call('Branch.set_config_option',
2780
path, self._branch._lock_token, self._branch._repo_lock_token,
2781
value.encode('utf8'), name, section or '')
2782
except errors.UnknownSmartMethod:
2783
medium._remember_remote_is_before((1, 14))
2784
return self._vfs_set_option(value, name, section)
2786
raise errors.UnexpectedSmartServerResponse(response)
2788
def _real_object(self):
2789
self._branch._ensure_real()
2790
return self._branch._real_branch
2792
def _vfs_set_option(self, value, name, section=None):
2793
return self._real_object()._get_config().set_option(
2794
value, name, section)
2797
class RemoteBzrDirConfig(RemoteConfig):
2798
"""A RemoteConfig for BzrDirs."""
2800
def __init__(self, bzrdir):
2801
self._bzrdir = bzrdir
2803
def _get_configobj(self):
2804
medium = self._bzrdir._client._medium
2805
verb = 'BzrDir.get_config_file'
2806
if medium._is_remote_before((1, 15)):
2807
raise errors.UnknownSmartMethod(verb)
2808
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2809
response = self._bzrdir._call_expecting_body(
2811
return self._response_to_configobj(response)
2813
def _vfs_get_option(self, name, section, default):
2814
return self._real_object()._get_config().get_option(
2815
name, section, default)
2817
def set_option(self, value, name, section=None):
2818
"""Set the value associated with a named option.
2820
:param value: The value to set
2821
:param name: The name of the value to set
2822
:param section: The section the option is in (if any)
2824
return self._real_object()._get_config().set_option(
2825
value, name, section)
2827
def _real_object(self):
2828
self._bzrdir._ensure_real()
2829
return self._bzrdir._real_bzrdir
1024
def update_revisions(self, other, stop_revision=None):
1026
return self._real_branch.update_revisions(
1027
other, stop_revision=stop_revision)
1030
class RemoteBranchConfig(BranchConfig):
1033
self.branch._ensure_real()
1034
return self.branch._real_branch.get_config().username()
1036
def _get_branch_data_config(self):
1037
self.branch._ensure_real()
1038
if self._branch_data_config is None:
1039
self._branch_data_config = TreeConfig(self.branch._real_branch)
1040
return self._branch_data_config
2833
1043
def _extract_tar(tar, to_dir):
2838
1048
for tarinfo in tar:
2839
1049
tar.extract(tarinfo, to_dir)
2842
def _translate_error(err, **context):
2843
"""Translate an ErrorFromSmartServer into a more useful error.
2845
Possible context keys:
2853
If the error from the server doesn't match a known pattern, then
2854
UnknownErrorFromSmartServer is raised.
2858
return context[name]
2859
except KeyError, key_err:
2860
mutter('Missing key %r in context %r', key_err.args[0], context)
2863
"""Get the path from the context if present, otherwise use first error
2867
return context['path']
2868
except KeyError, key_err:
2870
return err.error_args[0]
2871
except IndexError, idx_err:
2873
'Missing key %r in context %r', key_err.args[0], context)
2876
if err.error_verb == 'IncompatibleRepositories':
2877
raise errors.IncompatibleRepositories(err.error_args[0],
2878
err.error_args[1], err.error_args[2])
2879
elif err.error_verb == 'NoSuchRevision':
2880
raise NoSuchRevision(find('branch'), err.error_args[0])
2881
elif err.error_verb == 'nosuchrevision':
2882
raise NoSuchRevision(find('repository'), err.error_args[0])
2883
elif err.error_verb == 'nobranch':
2884
if len(err.error_args) >= 1:
2885
extra = err.error_args[0]
2888
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2890
elif err.error_verb == 'norepository':
2891
raise errors.NoRepositoryPresent(find('bzrdir'))
2892
elif err.error_verb == 'LockContention':
2893
raise errors.LockContention('(remote lock)')
2894
elif err.error_verb == 'UnlockableTransport':
2895
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2896
elif err.error_verb == 'LockFailed':
2897
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2898
elif err.error_verb == 'TokenMismatch':
2899
raise errors.TokenMismatch(find('token'), '(remote token)')
2900
elif err.error_verb == 'Diverged':
2901
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2902
elif err.error_verb == 'TipChangeRejected':
2903
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2904
elif err.error_verb == 'UnstackableBranchFormat':
2905
raise errors.UnstackableBranchFormat(*err.error_args)
2906
elif err.error_verb == 'UnstackableRepositoryFormat':
2907
raise errors.UnstackableRepositoryFormat(*err.error_args)
2908
elif err.error_verb == 'NotStacked':
2909
raise errors.NotStacked(branch=find('branch'))
2910
elif err.error_verb == 'PermissionDenied':
2912
if len(err.error_args) >= 2:
2913
extra = err.error_args[1]
2916
raise errors.PermissionDenied(path, extra=extra)
2917
elif err.error_verb == 'ReadError':
2919
raise errors.ReadError(path)
2920
elif err.error_verb == 'NoSuchFile':
2922
raise errors.NoSuchFile(path)
2923
elif err.error_verb == 'FileExists':
2924
raise errors.FileExists(err.error_args[0])
2925
elif err.error_verb == 'DirectoryNotEmpty':
2926
raise errors.DirectoryNotEmpty(err.error_args[0])
2927
elif err.error_verb == 'ShortReadvError':
2928
args = err.error_args
2929
raise errors.ShortReadvError(
2930
args[0], int(args[1]), int(args[2]), int(args[3]))
2931
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2932
encoding = str(err.error_args[0]) # encoding must always be a string
2933
val = err.error_args[1]
2934
start = int(err.error_args[2])
2935
end = int(err.error_args[3])
2936
reason = str(err.error_args[4]) # reason must always be a string
2937
if val.startswith('u:'):
2938
val = val[2:].decode('utf-8')
2939
elif val.startswith('s:'):
2940
val = val[2:].decode('base64')
2941
if err.error_verb == 'UnicodeDecodeError':
2942
raise UnicodeDecodeError(encoding, val, start, end, reason)
2943
elif err.error_verb == 'UnicodeEncodeError':
2944
raise UnicodeEncodeError(encoding, val, start, end, reason)
2945
elif err.error_verb == 'ReadOnlyError':
2946
raise errors.TransportNotPossible('readonly transport')
2947
raise errors.UnknownErrorFromSmartServer(err)