13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
20
from cStringIO import StringIO
19
22
from bzrlib import (
31
revision as _mod_revision,
34
from bzrlib.branch import BranchReferenceFormat
28
from bzrlib.branch import Branch, BranchReferenceFormat
35
29
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
36
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
37
from bzrlib.errors import (
30
from bzrlib.config import BranchConfig, TreeConfig
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
32
from bzrlib.errors import NoSuchRevision
41
33
from bzrlib.lockable_files import LockableFiles
42
from bzrlib.smart import client, vfs, repository as smart_repo
43
from bzrlib.revision import ensure_null, NULL_REVISION
44
from bzrlib.trace import mutter, note, warning
47
class _RpcHelper(object):
48
"""Mixin class that helps with issuing RPCs."""
50
def _call(self, method, *args, **err_context):
52
return self._client.call(method, *args)
53
except errors.ErrorFromSmartServer, err:
54
self._translate_error(err, **err_context)
56
def _call_expecting_body(self, method, *args, **err_context):
58
return self._client.call_expecting_body(method, *args)
59
except errors.ErrorFromSmartServer, err:
60
self._translate_error(err, **err_context)
62
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
64
return self._client.call_with_body_bytes(method, args, body_bytes)
65
except errors.ErrorFromSmartServer, err:
66
self._translate_error(err, **err_context)
68
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
71
return self._client.call_with_body_bytes_expecting_body(
72
method, args, body_bytes)
73
except errors.ErrorFromSmartServer, err:
74
self._translate_error(err, **err_context)
77
def response_tuple_to_repo_format(response):
78
"""Convert a response tuple describing a repository format to a format."""
79
format = RemoteRepositoryFormat()
80
format._rich_root_data = (response[0] == 'yes')
81
format._supports_tree_reference = (response[1] == 'yes')
82
format._supports_external_lookups = (response[2] == 'yes')
83
format._network_name = response[3]
34
from bzrlib.revision import NULL_REVISION
35
from bzrlib.smart import client, vfs
36
from bzrlib.trace import note
87
38
# Note: RemoteBzrDirFormat is in bzrdir.py
89
class RemoteBzrDir(BzrDir, _RpcHelper):
40
class RemoteBzrDir(BzrDir):
90
41
"""Control directory on a remote server, accessed via bzr:// or similar."""
92
def __init__(self, transport, format, _client=None, _force_probe=False):
43
def __init__(self, transport, _client=None):
93
44
"""Construct a RemoteBzrDir.
95
46
:param _client: Private parameter for testing. Disables probing and the
96
47
use of a real bzrdir.
98
BzrDir.__init__(self, transport, format)
49
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
99
50
# this object holds a delegated bzrdir that uses file-level operations
100
51
# to talk to the other side
101
52
self._real_bzrdir = None
102
self._has_working_tree = None
103
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
104
# create_branch for details.
105
self._next_open_branch_result = None
107
54
if _client is None:
108
medium = transport.get_smart_medium()
109
self._client = client._SmartClient(medium)
55
self._medium = transport.get_smart_client()
56
self._client = client._SmartClient(self._medium)
111
58
self._client = _client
118
return '%s(%r)' % (self.__class__.__name__, self._client)
120
def _probe_bzrdir(self):
121
medium = self._client._medium
122
62
path = self._path_for_remote_call(self._client)
123
if medium._is_remote_before((2, 1)):
127
self._rpc_open_2_1(path)
129
except errors.UnknownSmartMethod:
130
medium._remember_remote_is_before((2, 1))
133
def _rpc_open_2_1(self, path):
134
response = self._call('BzrDir.open_2.1', path)
135
if response == ('no',):
136
raise errors.NotBranchError(path=self.root_transport.base)
137
elif response[0] == 'yes':
138
if response[1] == 'yes':
139
self._has_working_tree = True
140
elif response[1] == 'no':
141
self._has_working_tree = False
143
raise errors.UnexpectedSmartServerResponse(response)
145
raise errors.UnexpectedSmartServerResponse(response)
147
def _rpc_open(self, path):
148
response = self._call('BzrDir.open', path)
63
response = self._client.call('BzrDir.open', path)
149
64
if response not in [('yes',), ('no',)]:
150
65
raise errors.UnexpectedSmartServerResponse(response)
151
66
if response == ('no',):
152
raise errors.NotBranchError(path=self.root_transport.base)
67
raise errors.NotBranchError(path=transport.base)
154
69
def _ensure_real(self):
155
70
"""Ensure that there is a _real_bzrdir set.
157
72
Used before calls to self._real_bzrdir.
159
74
if not self._real_bzrdir:
160
if 'hpssvfs' in debug.debug_flags:
162
warning('VFS BzrDir access triggered\n%s',
163
''.join(traceback.format_stack()))
164
75
self._real_bzrdir = BzrDir.open_from_transport(
165
76
self.root_transport, _server_formats=False)
166
self._format._network_name = \
167
self._real_bzrdir._format.network_name()
169
def _translate_error(self, err, **context):
170
_translate_error(err, bzrdir=self, **context)
172
def break_lock(self):
173
# Prevent aliasing problems in the next_open_branch_result cache.
174
# See create_branch for rationale.
175
self._next_open_branch_result = None
176
return BzrDir.break_lock(self)
178
def _vfs_cloning_metadir(self, require_stacking=False):
180
return self._real_bzrdir.cloning_metadir(
181
require_stacking=require_stacking)
183
def cloning_metadir(self, require_stacking=False):
184
medium = self._client._medium
185
if medium._is_remote_before((1, 13)):
186
return self._vfs_cloning_metadir(require_stacking=require_stacking)
187
verb = 'BzrDir.cloning_metadir'
192
path = self._path_for_remote_call(self._client)
194
response = self._call(verb, path, stacking)
195
except errors.UnknownSmartMethod:
196
medium._remember_remote_is_before((1, 13))
197
return self._vfs_cloning_metadir(require_stacking=require_stacking)
198
except errors.UnknownErrorFromSmartServer, err:
199
if err.error_tuple != ('BranchReference',):
201
# We need to resolve the branch reference to determine the
202
# cloning_metadir. This causes unnecessary RPCs to open the
203
# referenced branch (and bzrdir, etc) but only when the caller
204
# didn't already resolve the branch reference.
205
referenced_branch = self.open_branch()
206
return referenced_branch.bzrdir.cloning_metadir()
207
if len(response) != 3:
208
raise errors.UnexpectedSmartServerResponse(response)
209
control_name, repo_name, branch_info = response
210
if len(branch_info) != 2:
211
raise errors.UnexpectedSmartServerResponse(response)
212
branch_ref, branch_name = branch_info
213
format = bzrdir.network_format_registry.get(control_name)
215
format.repository_format = repository.network_format_registry.get(
217
if branch_ref == 'ref':
218
# XXX: we need possible_transports here to avoid reopening the
219
# connection to the referenced location
220
ref_bzrdir = BzrDir.open(branch_name)
221
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
222
format.set_branch_format(branch_format)
223
elif branch_ref == 'branch':
225
format.set_branch_format(
226
branch.network_format_registry.get(branch_name))
228
raise errors.UnexpectedSmartServerResponse(response)
231
78
def create_repository(self, shared=False):
232
# as per meta1 formats - just delegate to the format object which may
234
result = self._format.repository_format.initialize(self, shared)
235
if not isinstance(result, RemoteRepository):
236
return self.open_repository()
240
def destroy_repository(self):
241
"""See BzrDir.destroy_repository"""
242
79
self._ensure_real()
243
self._real_bzrdir.destroy_repository()
80
self._real_bzrdir.create_repository(shared=shared)
81
return self.open_repository()
245
83
def create_branch(self):
246
# as per meta1 formats - just delegate to the format object which may
248
real_branch = self._format.get_branch_format().initialize(self)
249
if not isinstance(real_branch, RemoteBranch):
250
result = RemoteBranch(self, self.find_repository(), real_branch)
253
# BzrDir.clone_on_transport() uses the result of create_branch but does
254
# not return it to its callers; we save approximately 8% of our round
255
# trips by handing the branch we created back to the first caller to
256
# open_branch rather than probing anew. Long term we need a API in
257
# bzrdir that doesn't discard result objects (like result_branch).
259
self._next_open_branch_result = result
262
def destroy_branch(self):
263
"""See BzrDir.destroy_branch"""
264
84
self._ensure_real()
265
self._real_bzrdir.destroy_branch()
266
self._next_open_branch_result = None
85
real_branch = self._real_bzrdir.create_branch()
86
return RemoteBranch(self, self.find_repository(), real_branch)
268
def create_workingtree(self, revision_id=None, from_branch=None):
88
def create_workingtree(self, revision_id=None):
269
89
raise errors.NotLocalUrl(self.transport.base)
271
91
def find_branch_format(self):
279
99
def get_branch_reference(self):
280
100
"""See BzrDir.get_branch_reference()."""
281
response = self._get_branch_reference()
282
if response[0] == 'ref':
287
def _get_branch_reference(self):
288
101
path = self._path_for_remote_call(self._client)
289
medium = self._client._medium
291
('BzrDir.open_branchV3', (2, 1)),
292
('BzrDir.open_branchV2', (1, 13)),
293
('BzrDir.open_branch', None),
295
for verb, required_version in candidate_calls:
296
if required_version and medium._is_remote_before(required_version):
299
response = self._call(verb, path)
300
except errors.UnknownSmartMethod:
301
if required_version is None:
303
medium._remember_remote_is_before(required_version)
306
if verb == 'BzrDir.open_branch':
307
if response[0] != 'ok':
308
raise errors.UnexpectedSmartServerResponse(response)
309
if response[1] != '':
310
return ('ref', response[1])
312
return ('branch', '')
313
if response[0] not in ('ref', 'branch'):
102
response = self._client.call('BzrDir.open_branch', path)
103
if response[0] == 'ok':
104
if response[1] == '':
105
# branch at this location.
108
# a branch reference, use the existing BranchReference logic.
110
elif response == ('nobranch',):
111
raise errors.NotBranchError(path=self.root_transport.base)
314
113
raise errors.UnexpectedSmartServerResponse(response)
317
def _get_tree_branch(self):
318
"""See BzrDir._get_tree_branch()."""
319
return None, self.open_branch()
321
def open_branch(self, _unsupported=False, ignore_fallbacks=False):
323
raise NotImplementedError('unsupported flag support not implemented yet.')
324
if self._next_open_branch_result is not None:
325
# See create_branch for details.
326
result = self._next_open_branch_result
327
self._next_open_branch_result = None
329
response = self._get_branch_reference()
330
if response[0] == 'ref':
115
def open_branch(self, _unsupported=False):
116
assert _unsupported == False, 'unsupported flag support not implemented yet.'
117
reference_url = self.get_branch_reference()
118
if reference_url is None:
119
# branch at this location.
120
return RemoteBranch(self, self.find_repository())
331
122
# a branch reference, use the existing BranchReference logic.
332
123
format = BranchReferenceFormat()
333
return format.open(self, _found=True, location=response[1],
334
ignore_fallbacks=ignore_fallbacks)
335
branch_format_name = response[1]
336
if not branch_format_name:
337
branch_format_name = None
338
format = RemoteBranchFormat(network_name=branch_format_name)
339
return RemoteBranch(self, self.find_repository(), format=format,
340
setup_stacking=not ignore_fallbacks)
342
def _open_repo_v1(self, path):
343
verb = 'BzrDir.find_repository'
344
response = self._call(verb, path)
345
if response[0] != 'ok':
346
raise errors.UnexpectedSmartServerResponse(response)
347
# servers that only support the v1 method don't support external
350
repo = self._real_bzrdir.open_repository()
351
response = response + ('no', repo._format.network_name())
352
return response, repo
354
def _open_repo_v2(self, path):
355
verb = 'BzrDir.find_repositoryV2'
356
response = self._call(verb, path)
357
if response[0] != 'ok':
358
raise errors.UnexpectedSmartServerResponse(response)
360
repo = self._real_bzrdir.open_repository()
361
response = response + (repo._format.network_name(),)
362
return response, repo
364
def _open_repo_v3(self, path):
365
verb = 'BzrDir.find_repositoryV3'
366
medium = self._client._medium
367
if medium._is_remote_before((1, 13)):
368
raise errors.UnknownSmartMethod(verb)
370
response = self._call(verb, path)
371
except errors.UnknownSmartMethod:
372
medium._remember_remote_is_before((1, 13))
374
if response[0] != 'ok':
375
raise errors.UnexpectedSmartServerResponse(response)
376
return response, None
124
return format.open(self, _found=True, location=reference_url)
378
126
def open_repository(self):
379
127
path = self._path_for_remote_call(self._client)
381
for probe in [self._open_repo_v3, self._open_repo_v2,
384
response, real_repo = probe(path)
386
except errors.UnknownSmartMethod:
389
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
390
if response[0] != 'ok':
391
raise errors.UnexpectedSmartServerResponse(response)
392
if len(response) != 6:
393
raise SmartProtocolError('incorrect response length %s' % (response,))
128
response = self._client.call('BzrDir.find_repository', path)
129
assert response[0] in ('ok', 'norepository'), \
130
'unexpected response code %s' % (response,)
131
if response[0] == 'norepository':
132
raise errors.NoRepositoryPresent(self)
133
assert len(response) == 4, 'incorrect response length %s' % (response,)
394
134
if response[1] == '':
395
# repo is at this dir.
396
format = response_tuple_to_repo_format(response[2:])
397
# Used to support creating a real format instance when needed.
398
format._creating_bzrdir = self
399
remote_repo = RemoteRepository(self, format)
400
format._creating_repo = remote_repo
401
if real_repo is not None:
402
remote_repo._set_real_repository(real_repo)
135
format = RemoteRepositoryFormat()
136
format.rich_root_data = (response[2] == 'yes')
137
format.supports_tree_reference = (response[3] == 'yes')
138
return RemoteRepository(self, format)
405
140
raise errors.NoRepositoryPresent(self)
407
def has_workingtree(self):
408
if self._has_working_tree is None:
410
self._has_working_tree = self._real_bzrdir.has_workingtree()
411
return self._has_working_tree
413
142
def open_workingtree(self, recommend_upgrade=True):
414
if self.has_workingtree():
144
if self._real_bzrdir.has_workingtree():
415
145
raise errors.NotLocalUrl(self.root_transport)
417
147
raise errors.NoWorkingTree(self.root_transport.base)
459
182
Instances of this repository are represented by RemoteRepository
462
The RemoteRepositoryFormat is parameterized during construction
185
The RemoteRepositoryFormat is parameterised during construction
463
186
to reflect the capabilities of the real, remote format. Specifically
464
187
the attributes rich_root_data and supports_tree_reference are set
465
188
on a per instance basis, and are not set (and should not be) at
468
:ivar _custom_format: If set, a specific concrete repository format that
469
will be used when initializing a repository with this
470
RemoteRepositoryFormat.
471
:ivar _creating_repo: If set, the repository object that this
472
RemoteRepositoryFormat was created for: it can be called into
473
to obtain data like the network name.
476
_matchingbzrdir = RemoteBzrDirFormat()
479
repository.RepositoryFormat.__init__(self)
480
self._custom_format = None
481
self._network_name = None
482
self._creating_bzrdir = None
483
self._supports_chks = None
484
self._supports_external_lookups = None
485
self._supports_tree_reference = None
486
self._rich_root_data = None
489
return "%s(_network_name=%r)" % (self.__class__.__name__,
493
def fast_deltas(self):
495
return self._custom_format.fast_deltas
498
def rich_root_data(self):
499
if self._rich_root_data is None:
501
self._rich_root_data = self._custom_format.rich_root_data
502
return self._rich_root_data
505
def supports_chks(self):
506
if self._supports_chks is None:
508
self._supports_chks = self._custom_format.supports_chks
509
return self._supports_chks
512
def supports_external_lookups(self):
513
if self._supports_external_lookups is None:
515
self._supports_external_lookups = \
516
self._custom_format.supports_external_lookups
517
return self._supports_external_lookups
520
def supports_tree_reference(self):
521
if self._supports_tree_reference is None:
523
self._supports_tree_reference = \
524
self._custom_format.supports_tree_reference
525
return self._supports_tree_reference
527
def _vfs_initialize(self, a_bzrdir, shared):
528
"""Helper for common code in initialize."""
529
if self._custom_format:
530
# Custom format requested
531
result = self._custom_format.initialize(a_bzrdir, shared=shared)
532
elif self._creating_bzrdir is not None:
533
# Use the format that the repository we were created to back
535
prior_repo = self._creating_bzrdir.open_repository()
536
prior_repo._ensure_real()
537
result = prior_repo._real_repository._format.initialize(
538
a_bzrdir, shared=shared)
540
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
541
# support remote initialization.
542
# We delegate to a real object at this point (as RemoteBzrDir
543
# delegate to the repository format which would lead to infinite
544
# recursion if we just called a_bzrdir.create_repository.
545
a_bzrdir._ensure_real()
546
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
547
if not isinstance(result, RemoteRepository):
548
return self.open(a_bzrdir)
192
_matchingbzrdir = RemoteBzrDirFormat
552
194
def initialize(self, a_bzrdir, shared=False):
553
# Being asked to create on a non RemoteBzrDir:
554
if not isinstance(a_bzrdir, RemoteBzrDir):
555
return self._vfs_initialize(a_bzrdir, shared)
556
medium = a_bzrdir._client._medium
557
if medium._is_remote_before((1, 13)):
558
return self._vfs_initialize(a_bzrdir, shared)
559
# Creating on a remote bzr dir.
560
# 1) get the network name to use.
561
if self._custom_format:
562
network_name = self._custom_format.network_name()
563
elif self._network_name:
564
network_name = self._network_name
566
# Select the current bzrlib default and ask for that.
567
reference_bzrdir_format = bzrdir.format_registry.get('default')()
568
reference_format = reference_bzrdir_format.repository_format
569
network_name = reference_format.network_name()
570
# 2) try direct creation via RPC
571
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
572
verb = 'BzrDir.create_repository'
578
response = a_bzrdir._call(verb, path, network_name, shared_str)
579
except errors.UnknownSmartMethod:
580
# Fallback - use vfs methods
581
medium._remember_remote_is_before((1, 13))
582
return self._vfs_initialize(a_bzrdir, shared)
584
# Turn the response into a RemoteRepository object.
585
format = response_tuple_to_repo_format(response[1:])
586
# Used to support creating a real format instance when needed.
587
format._creating_bzrdir = a_bzrdir
588
remote_repo = RemoteRepository(a_bzrdir, format)
589
format._creating_repo = remote_repo
195
assert isinstance(a_bzrdir, RemoteBzrDir), \
196
'%r is not a RemoteBzrDir' % (a_bzrdir,)
197
return a_bzrdir.create_repository(shared=shared)
592
199
def open(self, a_bzrdir):
593
if not isinstance(a_bzrdir, RemoteBzrDir):
594
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
200
assert isinstance(a_bzrdir, RemoteBzrDir)
595
201
return a_bzrdir.open_repository()
597
def _ensure_real(self):
598
if self._custom_format is None:
599
self._custom_format = repository.network_format_registry.get(
603
def _fetch_order(self):
605
return self._custom_format._fetch_order
608
def _fetch_uses_deltas(self):
610
return self._custom_format._fetch_uses_deltas
613
def _fetch_reconcile(self):
615
return self._custom_format._fetch_reconcile
617
203
def get_format_description(self):
619
return 'Remote: ' + self._custom_format.get_format_description()
204
return 'bzr remote repository'
621
206
def __eq__(self, other):
622
return self.__class__ is other.__class__
624
def network_name(self):
625
if self._network_name:
626
return self._network_name
627
self._creating_repo._ensure_real()
628
return self._creating_repo._real_repository._format.network_name()
631
def pack_compresses(self):
633
return self._custom_format.pack_compresses
636
def _serializer(self):
638
return self._custom_format._serializer
641
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin):
207
return self.__class__ == other.__class__
209
def check_conversion_target(self, target_format):
210
if self.rich_root_data and not target_format.rich_root_data:
211
raise errors.BadConversionTarget(
212
'Does not support rich root data.', target_format)
213
if (self.supports_tree_reference and
214
not getattr(target_format, 'supports_tree_reference', False)):
215
raise errors.BadConversionTarget(
216
'Does not support nested trees', target_format)
219
class RemoteRepository(object):
642
220
"""Repository accessed over rpc.
644
222
For the moment most operations are performed using local transport-backed
670
248
self._lock_token = None
671
249
self._lock_count = 0
672
250
self._leave_lock = False
673
# Cache of revision parents; misses are cached during read locks, and
674
# write locks when no _real_repository has been set.
675
self._unstacked_provider = graph.CachingParentsProvider(
676
get_parent_map=self._get_parent_map_rpc)
677
self._unstacked_provider.disable_cache()
679
# These depend on the actual remote format, so force them off for
680
# maximum compatibility. XXX: In future these should depend on the
681
# remote repository instance, but this is irrelevant until we perform
682
# reconcile via an RPC call.
683
self._reconcile_does_inventory_gc = False
684
self._reconcile_fixes_text_parents = False
685
self._reconcile_backsup_inventory = False
686
self.base = self.bzrdir.transport.base
687
# Additional places to query for data.
688
self._fallback_repositories = []
691
return "%s(%s)" % (self.__class__.__name__, self.base)
695
def abort_write_group(self, suppress_errors=False):
696
"""Complete a write group on the decorated repository.
698
Smart methods perform operations in a single step so this API
699
is not really applicable except as a compatibility thunk
700
for older plugins that don't use e.g. the CommitBuilder
703
:param suppress_errors: see Repository.abort_write_group.
706
return self._real_repository.abort_write_group(
707
suppress_errors=suppress_errors)
711
"""Decorate the real repository for now.
713
In the long term a full blown network facility is needed to avoid
714
creating a real repository object locally.
717
return self._real_repository.chk_bytes
719
def commit_write_group(self):
720
"""Complete a write group on the decorated repository.
722
Smart methods perform operations in a single step so this API
723
is not really applicable except as a compatibility thunk
724
for older plugins that don't use e.g. the CommitBuilder
728
return self._real_repository.commit_write_group()
730
def resume_write_group(self, tokens):
732
return self._real_repository.resume_write_group(tokens)
734
def suspend_write_group(self):
736
return self._real_repository.suspend_write_group()
738
def get_missing_parent_inventories(self, check_for_missing_texts=True):
740
return self._real_repository.get_missing_parent_inventories(
741
check_for_missing_texts=check_for_missing_texts)
743
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
745
return self._real_repository.get_rev_id_for_revno(
748
def get_rev_id_for_revno(self, revno, known_pair):
749
"""See Repository.get_rev_id_for_revno."""
750
path = self.bzrdir._path_for_remote_call(self._client)
752
if self._client._medium._is_remote_before((1, 17)):
753
return self._get_rev_id_for_revno_vfs(revno, known_pair)
754
response = self._call(
755
'Repository.get_rev_id_for_revno', path, revno, known_pair)
756
except errors.UnknownSmartMethod:
757
self._client._medium._remember_remote_is_before((1, 17))
758
return self._get_rev_id_for_revno_vfs(revno, known_pair)
759
if response[0] == 'ok':
760
return True, response[1]
761
elif response[0] == 'history-incomplete':
762
known_pair = response[1:3]
763
for fallback in self._fallback_repositories:
764
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
769
# Not found in any fallbacks
770
return False, known_pair
772
raise errors.UnexpectedSmartServerResponse(response)
774
252
def _ensure_real(self):
775
253
"""Ensure that there is a _real_repository set.
777
255
Used before calls to self._real_repository.
779
Note that _ensure_real causes many roundtrips to the server which are
780
not desirable, and prevents the use of smart one-roundtrip RPC's to
781
perform complex operations (such as accessing parent data, streaming
782
revisions etc). Adding calls to _ensure_real should only be done when
783
bringing up new functionality, adding fallbacks for smart methods that
784
require a fallback path, and never to replace an existing smart method
785
invocation. If in doubt chat to the bzr network team.
787
if self._real_repository is None:
788
if 'hpssvfs' in debug.debug_flags:
790
warning('VFS Repository access triggered\n%s',
791
''.join(traceback.format_stack()))
792
self._unstacked_provider.missing_keys.clear()
257
if not self._real_repository:
793
258
self.bzrdir._ensure_real()
794
self._set_real_repository(
795
self.bzrdir._real_bzrdir.open_repository())
797
def _translate_error(self, err, **context):
798
self.bzrdir._translate_error(err, repository=self, **context)
800
def find_text_key_references(self):
801
"""Find the text key references within the repository.
803
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
804
revision_ids. Each altered file-ids has the exact revision_ids that
805
altered it listed explicitly.
806
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
807
to whether they were referred to by the inventory of the
808
revision_id that they contain. The inventory texts from all present
809
revision ids are assessed to generate this report.
812
return self._real_repository.find_text_key_references()
814
def _generate_text_key_index(self):
815
"""Generate a new text key index for the repository.
817
This is an expensive function that will take considerable time to run.
819
:return: A dict mapping (file_id, revision_id) tuples to a list of
820
parents, also (file_id, revision_id) tuples.
823
return self._real_repository._generate_text_key_index()
825
def _get_revision_graph(self, revision_id):
826
"""Private method for using with old (< 1.2) servers to fallback."""
259
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
260
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
262
def get_revision_graph(self, revision_id=None):
263
"""See Repository.get_revision_graph()."""
827
264
if revision_id is None:
829
elif revision.is_null(revision_id):
266
elif revision_id == NULL_REVISION:
832
269
path = self.bzrdir._path_for_remote_call(self._client)
833
response = self._call_expecting_body(
270
assert type(revision_id) is str
271
response = self._client.call_expecting_body(
834
272
'Repository.get_revision_graph', path, revision_id)
835
response_tuple, response_handler = response
836
if response_tuple[0] != 'ok':
837
raise errors.UnexpectedSmartServerResponse(response_tuple)
838
coded = response_handler.read_body_bytes()
840
# no revisions in this repository!
842
lines = coded.split('\n')
845
d = tuple(line.split())
846
revision_graph[d[0]] = d[1:]
848
return revision_graph
851
"""See Repository._get_sink()."""
852
return RemoteStreamSink(self)
854
def _get_source(self, to_format):
855
"""Return a source for streaming from this repository."""
856
return RemoteStreamSource(self, to_format)
273
if response[0][0] not in ['ok', 'nosuchrevision']:
274
raise errors.UnexpectedSmartServerResponse(response[0])
275
if response[0][0] == 'ok':
276
coded = response[1].read_body_bytes()
278
# no revisions in this repository!
280
lines = coded.split('\n')
283
d = list(line.split())
284
revision_graph[d[0]] = d[1:]
286
return revision_graph
288
response_body = response[1].read_body_bytes()
289
assert response_body == ''
290
raise NoSuchRevision(self, revision_id)
859
292
def has_revision(self, revision_id):
860
"""True if this repository has a copy of the revision."""
861
# Copy of bzrlib.repository.Repository.has_revision
862
return revision_id in self.has_revisions((revision_id,))
865
def has_revisions(self, revision_ids):
866
"""Probe to find out the presence of multiple revisions.
868
:param revision_ids: An iterable of revision_ids.
869
:return: A set of the revision_ids that were present.
871
# Copy of bzrlib.repository.Repository.has_revisions
872
parent_map = self.get_parent_map(revision_ids)
873
result = set(parent_map)
874
if _mod_revision.NULL_REVISION in revision_ids:
875
result.add(_mod_revision.NULL_REVISION)
878
def _has_same_fallbacks(self, other_repo):
879
"""Returns true if the repositories have the same fallbacks."""
880
# XXX: copied from Repository; it should be unified into a base class
881
# <https://bugs.edge.launchpad.net/bzr/+bug/401622>
882
my_fb = self._fallback_repositories
883
other_fb = other_repo._fallback_repositories
884
if len(my_fb) != len(other_fb):
886
for f, g in zip(my_fb, other_fb):
887
if not f.has_same_location(g):
891
def has_same_location(self, other):
892
# TODO: Move to RepositoryBase and unify with the regular Repository
893
# one; unfortunately the tests rely on slightly different behaviour at
894
# present -- mbp 20090710
895
return (self.__class__ is other.__class__ and
896
self.bzrdir.transport.base == other.bzrdir.transport.base)
293
"""See Repository.has_revision()."""
294
if revision_id is None:
295
# The null revision is always present.
297
path = self.bzrdir._path_for_remote_call(self._client)
298
response = self._client.call('Repository.has_revision', path, revision_id)
299
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
300
return response[0] == 'yes'
898
302
def get_graph(self, other_repository=None):
899
303
"""Return the graph for this repository format"""
900
parents_provider = self._make_parents_provider(other_repository)
901
return graph.Graph(parents_provider)
304
return self._real_repository.get_graph(other_repository)
903
306
def gather_stats(self, revid=None, committers=None):
904
307
"""See Repository.gather_stats()."""
905
308
path = self.bzrdir._path_for_remote_call(self._client)
906
# revid can be None to indicate no revisions, not just NULL_REVISION
907
if revid is None or revision.is_null(revid):
309
if revid in (None, NULL_REVISION):
910
312
fmt_revid = revid
1192
480
# FIXME: It ought to be possible to call this without immediately
1193
481
# triggering _ensure_real. For now it's the easiest thing to do.
1194
482
self._ensure_real()
1195
real_repo = self._real_repository
1196
builder = real_repo.get_commit_builder(branch, parents,
483
builder = self._real_repository.get_commit_builder(branch, parents,
1197
484
config, timestamp=timestamp, timezone=timezone,
1198
485
committer=committer, revprops=revprops, revision_id=revision_id)
486
# Make the builder use this RemoteRepository rather than the real one.
487
builder.repository = self
1201
def add_fallback_repository(self, repository):
1202
"""Add a repository to use for looking up data not held locally.
1204
:param repository: A repository.
1206
if not self._format.supports_external_lookups:
1207
raise errors.UnstackableRepositoryFormat(
1208
self._format.network_name(), self.base)
1209
# We need to accumulate additional repositories here, to pass them in
1212
if self.is_locked():
1213
# We will call fallback.unlock() when we transition to the unlocked
1214
# state, so always add a lock here. If a caller passes us a locked
1215
# repository, they are responsible for unlocking it later.
1216
repository.lock_read()
1217
self._fallback_repositories.append(repository)
1218
# If self._real_repository was parameterised already (e.g. because a
1219
# _real_branch had its get_stacked_on_url method called), then the
1220
# repository to be added may already be in the _real_repositories list.
1221
if self._real_repository is not None:
1222
fallback_locations = [repo.bzrdir.root_transport.base for repo in
1223
self._real_repository._fallback_repositories]
1224
if repository.bzrdir.root_transport.base not in fallback_locations:
1225
self._real_repository.add_fallback_repository(repository)
1227
491
def add_inventory(self, revid, inv, parents):
1228
492
self._ensure_real()
1229
493
return self._real_repository.add_inventory(revid, inv, parents)
1231
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1234
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1235
delta, new_revision_id, parents)
1237
496
def add_revision(self, rev_id, rev, inv=None, config=None):
1238
497
self._ensure_real()
1239
498
return self._real_repository.add_revision(
1263
523
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1265
525
def make_working_trees(self):
1266
"""See Repository.make_working_trees"""
526
"""RemoteRepositories never create working trees by default."""
529
def fetch(self, source, revision_id=None, pb=None):
1267
530
self._ensure_real()
1268
return self._real_repository.make_working_trees()
1270
def refresh_data(self):
1271
"""Re-read any data needed to to synchronise with disk.
1273
This method is intended to be called after another repository instance
1274
(such as one used by a smart server) has inserted data into the
1275
repository. It may not be called during a write group, but may be
1276
called at any other time.
1278
if self.is_in_write_group():
1279
raise errors.InternalBzrError(
1280
"May not refresh_data while in a write group.")
1281
if self._real_repository is not None:
1282
self._real_repository.refresh_data()
1284
def revision_ids_to_search_result(self, result_set):
1285
"""Convert a set of revision ids to a graph SearchResult."""
1286
result_parents = set()
1287
for parents in self.get_graph().get_parent_map(
1288
result_set).itervalues():
1289
result_parents.update(parents)
1290
included_keys = result_set.intersection(result_parents)
1291
start_keys = result_set.difference(included_keys)
1292
exclude_keys = result_parents.difference(result_set)
1293
result = graph.SearchResult(start_keys, exclude_keys,
1294
len(result_set), result_set)
1298
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1299
"""Return the revision ids that other has that this does not.
1301
These are returned in topological order.
1303
revision_id: only return revision ids included by revision_id.
1305
return repository.InterRepository.get(
1306
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1308
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1310
# No base implementation to use as RemoteRepository is not a subclass
1311
# of Repository; so this is a copy of Repository.fetch().
1312
if fetch_spec is not None and revision_id is not None:
1313
raise AssertionError(
1314
"fetch_spec and revision_id are mutually exclusive.")
1315
if self.is_in_write_group():
1316
raise errors.InternalBzrError(
1317
"May not fetch while in a write group.")
1318
# fast path same-url fetch operations
1319
if (self.has_same_location(source)
1320
and fetch_spec is None
1321
and self._has_same_fallbacks(source)):
1322
# check that last_revision is in 'from' and then return a
1324
if (revision_id is not None and
1325
not revision.is_null(revision_id)):
1326
self.get_revision(revision_id)
1328
# if there is no specific appropriate InterRepository, this will get
1329
# the InterRepository base class, which raises an
1330
# IncompatibleRepositories when asked to fetch.
1331
inter = repository.InterRepository.get(source, self)
1332
return inter.fetch(revision_id=revision_id, pb=pb,
1333
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
531
return self._real_repository.fetch(
532
source, revision_id=revision_id, pb=pb)
1335
534
def create_bundle(self, target, base, fileobj, format=None):
1336
535
self._ensure_real()
1337
536
self._real_repository.create_bundle(target, base, fileobj, format)
539
def control_weaves(self):
541
return self._real_repository.control_weaves
1339
543
@needs_read_lock
1340
544
def get_ancestry(self, revision_id, topo_sorted=True):
1341
545
self._ensure_real()
1342
546
return self._real_repository.get_ancestry(revision_id, topo_sorted)
549
def get_inventory_weave(self):
551
return self._real_repository.get_inventory_weave()
1344
553
def fileids_altered_by_revision_ids(self, revision_ids):
1345
554
self._ensure_real()
1346
555
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1348
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1350
return self._real_repository._get_versioned_file_checker(
1351
revisions, revision_versions_cache)
1353
def iter_files_bytes(self, desired_files):
1354
"""See Repository.iter_file_bytes.
1357
return self._real_repository.iter_files_bytes(desired_files)
1359
def get_parent_map(self, revision_ids):
1360
"""See bzrlib.Graph.get_parent_map()."""
1361
return self._make_parents_provider().get_parent_map(revision_ids)
1363
def _get_parent_map_rpc(self, keys):
1364
"""Helper for get_parent_map that performs the RPC."""
1365
medium = self._client._medium
1366
if medium._is_remote_before((1, 2)):
1367
# We already found out that the server can't understand
1368
# Repository.get_parent_map requests, so just fetch the whole
1371
# Note that this reads the whole graph, when only some keys are
1372
# wanted. On this old server there's no way (?) to get them all
1373
# in one go, and the user probably will have seen a warning about
1374
# the server being old anyhow.
1375
rg = self._get_revision_graph(None)
1376
# There is an API discrepancy between get_parent_map and
1377
# get_revision_graph. Specifically, a "key:()" pair in
1378
# get_revision_graph just means a node has no parents. For
1379
# "get_parent_map" it means the node is a ghost. So fix up the
1380
# graph to correct this.
1381
# https://bugs.launchpad.net/bzr/+bug/214894
1382
# There is one other "bug" which is that ghosts in
1383
# get_revision_graph() are not returned at all. But we won't worry
1384
# about that for now.
1385
for node_id, parent_ids in rg.iteritems():
1386
if parent_ids == ():
1387
rg[node_id] = (NULL_REVISION,)
1388
rg[NULL_REVISION] = ()
1393
raise ValueError('get_parent_map(None) is not valid')
1394
if NULL_REVISION in keys:
1395
keys.discard(NULL_REVISION)
1396
found_parents = {NULL_REVISION:()}
1398
return found_parents
1401
# TODO(Needs analysis): We could assume that the keys being requested
1402
# from get_parent_map are in a breadth first search, so typically they
1403
# will all be depth N from some common parent, and we don't have to
1404
# have the server iterate from the root parent, but rather from the
1405
# keys we're searching; and just tell the server the keyspace we
1406
# already have; but this may be more traffic again.
1408
# Transform self._parents_map into a search request recipe.
1409
# TODO: Manage this incrementally to avoid covering the same path
1410
# repeatedly. (The server will have to on each request, but the less
1411
# work done the better).
1413
# Negative caching notes:
1414
# new server sends missing when a request including the revid
1415
# 'include-missing:' is present in the request.
1416
# missing keys are serialised as missing:X, and we then call
1417
# provider.note_missing(X) for-all X
1418
parents_map = self._unstacked_provider.get_cached_map()
1419
if parents_map is None:
1420
# Repository is not locked, so there's no cache.
1422
# start_set is all the keys in the cache
1423
start_set = set(parents_map)
1424
# result set is all the references to keys in the cache
1425
result_parents = set()
1426
for parents in parents_map.itervalues():
1427
result_parents.update(parents)
1428
stop_keys = result_parents.difference(start_set)
1429
# We don't need to send ghosts back to the server as a position to
1431
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1432
key_count = len(parents_map)
1433
if (NULL_REVISION in result_parents
1434
and NULL_REVISION in self._unstacked_provider.missing_keys):
1435
# If we pruned NULL_REVISION from the stop_keys because it's also
1436
# in our cache of "missing" keys we need to increment our key count
1437
# by 1, because the reconsitituted SearchResult on the server will
1438
# still consider NULL_REVISION to be an included key.
1440
included_keys = start_set.intersection(result_parents)
1441
start_set.difference_update(included_keys)
1442
recipe = ('manual', start_set, stop_keys, key_count)
1443
body = self._serialise_search_recipe(recipe)
1444
path = self.bzrdir._path_for_remote_call(self._client)
1446
if type(key) is not str:
1448
"key %r not a plain string" % (key,))
1449
verb = 'Repository.get_parent_map'
1450
args = (path, 'include-missing:') + tuple(keys)
1452
response = self._call_with_body_bytes_expecting_body(
1454
except errors.UnknownSmartMethod:
1455
# Server does not support this method, so get the whole graph.
1456
# Worse, we have to force a disconnection, because the server now
1457
# doesn't realise it has a body on the wire to consume, so the
1458
# only way to recover is to abandon the connection.
1460
'Server is too old for fast get_parent_map, reconnecting. '
1461
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1463
# To avoid having to disconnect repeatedly, we keep track of the
1464
# fact the server doesn't understand remote methods added in 1.2.
1465
medium._remember_remote_is_before((1, 2))
1466
# Recurse just once and we should use the fallback code.
1467
return self._get_parent_map_rpc(keys)
1468
response_tuple, response_handler = response
1469
if response_tuple[0] not in ['ok']:
1470
response_handler.cancel_read_body()
1471
raise errors.UnexpectedSmartServerResponse(response_tuple)
1472
if response_tuple[0] == 'ok':
1473
coded = bz2.decompress(response_handler.read_body_bytes())
1475
# no revisions found
1477
lines = coded.split('\n')
1480
d = tuple(line.split())
1482
revision_graph[d[0]] = d[1:]
1485
if d[0].startswith('missing:'):
1487
self._unstacked_provider.note_missing_key(revid)
1489
# no parents - so give the Graph result
1491
revision_graph[d[0]] = (NULL_REVISION,)
1492
return revision_graph
1494
557
@needs_read_lock
1495
558
def get_signature_text(self, revision_id):
1496
559
self._ensure_real()
1497
560
return self._real_repository.get_signature_text(revision_id)
1499
562
@needs_read_lock
1500
def _get_inventory_xml(self, revision_id):
1502
return self._real_repository._get_inventory_xml(revision_id)
1504
def _deserialise_inventory(self, revision_id, xml):
1506
return self._real_repository._deserialise_inventory(revision_id, xml)
563
def get_revision_graph_with_ghosts(self, revision_ids=None):
565
return self._real_repository.get_revision_graph_with_ghosts(
566
revision_ids=revision_ids)
569
def get_inventory_xml(self, revision_id):
571
return self._real_repository.get_inventory_xml(revision_id)
573
def deserialise_inventory(self, revision_id, xml):
575
return self._real_repository.deserialise_inventory(revision_id, xml)
1508
577
def reconcile(self, other=None, thorough=False):
1509
578
self._ensure_real()
1510
579
return self._real_repository.reconcile(other=other, thorough=thorough)
1512
581
def all_revision_ids(self):
1513
582
self._ensure_real()
1514
583
return self._real_repository.all_revision_ids()
1517
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1519
return self._real_repository.get_deltas_for_revisions(revisions,
1520
specific_fileids=specific_fileids)
1523
def get_revision_delta(self, revision_id, specific_fileids=None):
1525
return self._real_repository.get_revision_delta(revision_id,
1526
specific_fileids=specific_fileids)
586
def get_deltas_for_revisions(self, revisions):
588
return self._real_repository.get_deltas_for_revisions(revisions)
591
def get_revision_delta(self, revision_id):
593
return self._real_repository.get_revision_delta(revision_id)
1528
595
@needs_read_lock
1529
596
def revision_trees(self, revision_ids):
1662
675
def _serializer(self):
1663
return self._format._serializer
677
return self._real_repository._serializer
1665
679
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1666
680
self._ensure_real()
1667
681
return self._real_repository.store_revision_signature(
1668
682
gpg_strategy, plaintext, revision_id)
1670
def add_signature_text(self, revision_id, signature):
1672
return self._real_repository.add_signature_text(revision_id, signature)
1674
684
def has_signature_for_revision_id(self, revision_id):
1675
685
self._ensure_real()
1676
686
return self._real_repository.has_signature_for_revision_id(revision_id)
1678
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1680
return self._real_repository.item_keys_introduced_by(revision_ids,
1681
_files_pb=_files_pb)
1683
def revision_graph_can_have_wrong_parents(self):
1684
# The answer depends on the remote repo format.
1686
return self._real_repository.revision_graph_can_have_wrong_parents()
1688
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1690
return self._real_repository._find_inconsistent_revision_parents(
1693
def _check_for_inconsistent_revision_parents(self):
1695
return self._real_repository._check_for_inconsistent_revision_parents()
1697
def _make_parents_provider(self, other=None):
1698
providers = [self._unstacked_provider]
1699
if other is not None:
1700
providers.insert(0, other)
1701
providers.extend(r._make_parents_provider() for r in
1702
self._fallback_repositories)
1703
return graph.StackedParentsProvider(providers)
1705
def _serialise_search_recipe(self, recipe):
1706
"""Serialise a graph search recipe.
1708
:param recipe: A search recipe (start, stop, count).
1709
:return: Serialised bytes.
1711
start_keys = ' '.join(recipe[1])
1712
stop_keys = ' '.join(recipe[2])
1713
count = str(recipe[3])
1714
return '\n'.join((start_keys, stop_keys, count))
1716
def _serialise_search_result(self, search_result):
1717
if isinstance(search_result, graph.PendingAncestryResult):
1718
parts = ['ancestry-of']
1719
parts.extend(search_result.heads)
1721
recipe = search_result.get_recipe()
1722
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1723
return '\n'.join(parts)
1726
path = self.bzrdir._path_for_remote_call(self._client)
1728
response = self._call('PackRepository.autopack', path)
1729
except errors.UnknownSmartMethod:
1731
self._real_repository._pack_collection.autopack()
1734
if response[0] != 'ok':
1735
raise errors.UnexpectedSmartServerResponse(response)
1738
class RemoteStreamSink(repository.StreamSink):
1740
def _insert_real(self, stream, src_format, resume_tokens):
1741
self.target_repo._ensure_real()
1742
sink = self.target_repo._real_repository._get_sink()
1743
result = sink.insert_stream(stream, src_format, resume_tokens)
1745
self.target_repo.autopack()
1748
def insert_stream(self, stream, src_format, resume_tokens):
1749
target = self.target_repo
1750
target._unstacked_provider.missing_keys.clear()
1751
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1752
if target._lock_token:
1753
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1754
lock_args = (target._lock_token or '',)
1756
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1758
client = target._client
1759
medium = client._medium
1760
path = target.bzrdir._path_for_remote_call(client)
1761
# Probe for the verb to use with an empty stream before sending the
1762
# real stream to it. We do this both to avoid the risk of sending a
1763
# large request that is then rejected, and because we don't want to
1764
# implement a way to buffer, rewind, or restart the stream.
1766
for verb, required_version in candidate_calls:
1767
if medium._is_remote_before(required_version):
1770
# We've already done the probing (and set _is_remote_before) on
1771
# a previous insert.
1774
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1776
response = client.call_with_body_stream(
1777
(verb, path, '') + lock_args, byte_stream)
1778
except errors.UnknownSmartMethod:
1779
medium._remember_remote_is_before(required_version)
1785
return self._insert_real(stream, src_format, resume_tokens)
1786
self._last_inv_record = None
1787
self._last_substream = None
1788
if required_version < (1, 19):
1789
# Remote side doesn't support inventory deltas. Wrap the stream to
1790
# make sure we don't send any. If the stream contains inventory
1791
# deltas we'll interrupt the smart insert_stream request and
1793
stream = self._stop_stream_if_inventory_delta(stream)
1794
byte_stream = smart_repo._stream_to_byte_stream(
1796
resume_tokens = ' '.join(resume_tokens)
1797
response = client.call_with_body_stream(
1798
(verb, path, resume_tokens) + lock_args, byte_stream)
1799
if response[0][0] not in ('ok', 'missing-basis'):
1800
raise errors.UnexpectedSmartServerResponse(response)
1801
if self._last_substream is not None:
1802
# The stream included an inventory-delta record, but the remote
1803
# side isn't new enough to support them. So we need to send the
1804
# rest of the stream via VFS.
1805
self.target_repo.refresh_data()
1806
return self._resume_stream_with_vfs(response, src_format)
1807
if response[0][0] == 'missing-basis':
1808
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1809
resume_tokens = tokens
1810
return resume_tokens, set(missing_keys)
1812
self.target_repo.refresh_data()
1815
def _resume_stream_with_vfs(self, response, src_format):
1816
"""Resume sending a stream via VFS, first resending the record and
1817
substream that couldn't be sent via an insert_stream verb.
1819
if response[0][0] == 'missing-basis':
1820
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1821
# Ignore missing_keys, we haven't finished inserting yet
1824
def resume_substream():
1825
# Yield the substream that was interrupted.
1826
for record in self._last_substream:
1828
self._last_substream = None
1829
def resume_stream():
1830
# Finish sending the interrupted substream
1831
yield ('inventory-deltas', resume_substream())
1832
# Then simply continue sending the rest of the stream.
1833
for substream_kind, substream in self._last_stream:
1834
yield substream_kind, substream
1835
return self._insert_real(resume_stream(), src_format, tokens)
1837
def _stop_stream_if_inventory_delta(self, stream):
1838
"""Normally this just lets the original stream pass-through unchanged.
1840
However if any 'inventory-deltas' substream occurs it will stop
1841
streaming, and store the interrupted substream and stream in
1842
self._last_substream and self._last_stream so that the stream can be
1843
resumed by _resume_stream_with_vfs.
1846
stream_iter = iter(stream)
1847
for substream_kind, substream in stream_iter:
1848
if substream_kind == 'inventory-deltas':
1849
self._last_substream = substream
1850
self._last_stream = stream_iter
1853
yield substream_kind, substream
1856
class RemoteStreamSource(repository.StreamSource):
1857
"""Stream data from a remote server."""
1859
def get_stream(self, search):
1860
if (self.from_repository._fallback_repositories and
1861
self.to_format._fetch_order == 'topological'):
1862
return self._real_stream(self.from_repository, search)
1865
repos = [self.from_repository]
1871
repos.extend(repo._fallback_repositories)
1872
sources.append(repo)
1873
return self.missing_parents_chain(search, sources)
1875
def get_stream_for_missing_keys(self, missing_keys):
1876
self.from_repository._ensure_real()
1877
real_repo = self.from_repository._real_repository
1878
real_source = real_repo._get_source(self.to_format)
1879
return real_source.get_stream_for_missing_keys(missing_keys)
1881
def _real_stream(self, repo, search):
1882
"""Get a stream for search from repo.
1884
This never called RemoteStreamSource.get_stream, and is a heler
1885
for RemoteStreamSource._get_stream to allow getting a stream
1886
reliably whether fallback back because of old servers or trying
1887
to stream from a non-RemoteRepository (which the stacked support
1890
source = repo._get_source(self.to_format)
1891
if isinstance(source, RemoteStreamSource):
1893
source = repo._real_repository._get_source(self.to_format)
1894
return source.get_stream(search)
1896
def _get_stream(self, repo, search):
1897
"""Core worker to get a stream from repo for search.
1899
This is used by both get_stream and the stacking support logic. It
1900
deliberately gets a stream for repo which does not need to be
1901
self.from_repository. In the event that repo is not Remote, or
1902
cannot do a smart stream, a fallback is made to the generic
1903
repository._get_stream() interface, via self._real_stream.
1905
In the event of stacking, streams from _get_stream will not
1906
contain all the data for search - this is normal (see get_stream).
1908
:param repo: A repository.
1909
:param search: A search.
1911
# Fallbacks may be non-smart
1912
if not isinstance(repo, RemoteRepository):
1913
return self._real_stream(repo, search)
1914
client = repo._client
1915
medium = client._medium
1916
path = repo.bzrdir._path_for_remote_call(client)
1917
search_bytes = repo._serialise_search_result(search)
1918
args = (path, self.to_format.network_name())
1920
('Repository.get_stream_1.19', (1, 19)),
1921
('Repository.get_stream', (1, 13))]
1923
for verb, version in candidate_verbs:
1924
if medium._is_remote_before(version):
1927
response = repo._call_with_body_bytes_expecting_body(
1928
verb, args, search_bytes)
1929
except errors.UnknownSmartMethod:
1930
medium._remember_remote_is_before(version)
1932
response_tuple, response_handler = response
1936
return self._real_stream(repo, search)
1937
if response_tuple[0] != 'ok':
1938
raise errors.UnexpectedSmartServerResponse(response_tuple)
1939
byte_stream = response_handler.read_streamed_body()
1940
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream)
1941
if src_format.network_name() != repo._format.network_name():
1942
raise AssertionError(
1943
"Mismatched RemoteRepository and stream src %r, %r" % (
1944
src_format.network_name(), repo._format.network_name()))
1947
def missing_parents_chain(self, search, sources):
1948
"""Chain multiple streams together to handle stacking.
1950
:param search: The overall search to satisfy with streams.
1951
:param sources: A list of Repository objects to query.
1953
self.from_serialiser = self.from_repository._format._serializer
1954
self.seen_revs = set()
1955
self.referenced_revs = set()
1956
# If there are heads in the search, or the key count is > 0, we are not
1958
while not search.is_empty() and len(sources) > 1:
1959
source = sources.pop(0)
1960
stream = self._get_stream(source, search)
1961
for kind, substream in stream:
1962
if kind != 'revisions':
1963
yield kind, substream
1965
yield kind, self.missing_parents_rev_handler(substream)
1966
search = search.refine(self.seen_revs, self.referenced_revs)
1967
self.seen_revs = set()
1968
self.referenced_revs = set()
1969
if not search.is_empty():
1970
for kind, stream in self._get_stream(sources[0], search):
1973
def missing_parents_rev_handler(self, substream):
1974
for content in substream:
1975
revision_bytes = content.get_bytes_as('fulltext')
1976
revision = self.from_serialiser.read_revision_from_string(
1978
self.seen_revs.add(content.key[-1])
1979
self.referenced_revs.update(revision.parent_ids)
1983
689
class RemoteBranchLockableFiles(LockableFiles):
1984
690
"""A 'LockableFiles' implementation that talks to a smart server.
1986
692
This is not a public interface class.
1999
705
self._dir_mode = None
2000
706
self._file_mode = None
709
"""'get' a remote path as per the LockableFiles interface.
711
:param path: the file to 'get'. If this is 'branch.conf', we do not
712
just retrieve a file, instead we ask the smart server to generate
713
a configuration for us - which is retrieved as an INI file.
715
if path == 'branch.conf':
716
path = self.bzrdir._path_for_remote_call(self._client)
717
response = self._client.call_expecting_body(
718
'Branch.get_config_file', path)
719
assert response[0][0] == 'ok', \
720
'unexpected response code %s' % (response[0],)
721
return StringIO(response[1].read_body_bytes())
724
return LockableFiles.get(self, path)
2003
727
class RemoteBranchFormat(branch.BranchFormat):
2005
def __init__(self, network_name=None):
2006
super(RemoteBranchFormat, self).__init__()
2007
self._matchingbzrdir = RemoteBzrDirFormat()
2008
self._matchingbzrdir.set_branch_format(self)
2009
self._custom_format = None
2010
self._network_name = network_name
2012
729
def __eq__(self, other):
2013
return (isinstance(other, RemoteBranchFormat) and
730
return (isinstance(other, RemoteBranchFormat) and
2014
731
self.__dict__ == other.__dict__)
2016
def _ensure_real(self):
2017
if self._custom_format is None:
2018
self._custom_format = branch.network_format_registry.get(
2021
733
def get_format_description(self):
2023
return 'Remote: ' + self._custom_format.get_format_description()
2025
def network_name(self):
2026
return self._network_name
2028
def open(self, a_bzrdir, ignore_fallbacks=False):
2029
return a_bzrdir.open_branch(ignore_fallbacks=ignore_fallbacks)
2031
def _vfs_initialize(self, a_bzrdir):
2032
# Initialisation when using a local bzrdir object, or a non-vfs init
2033
# method is not available on the server.
2034
# self._custom_format is always set - the start of initialize ensures
2036
if isinstance(a_bzrdir, RemoteBzrDir):
2037
a_bzrdir._ensure_real()
2038
result = self._custom_format.initialize(a_bzrdir._real_bzrdir)
2040
# We assume the bzrdir is parameterised; it may not be.
2041
result = self._custom_format.initialize(a_bzrdir)
2042
if (isinstance(a_bzrdir, RemoteBzrDir) and
2043
not isinstance(result, RemoteBranch)):
2044
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result)
734
return 'Remote BZR Branch'
736
def get_format_string(self):
737
return 'Remote BZR Branch'
739
def open(self, a_bzrdir):
740
assert isinstance(a_bzrdir, RemoteBzrDir)
741
return a_bzrdir.open_branch()
2047
743
def initialize(self, a_bzrdir):
2048
# 1) get the network name to use.
2049
if self._custom_format:
2050
network_name = self._custom_format.network_name()
2052
# Select the current bzrlib default and ask for that.
2053
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2054
reference_format = reference_bzrdir_format.get_branch_format()
2055
self._custom_format = reference_format
2056
network_name = reference_format.network_name()
2057
# Being asked to create on a non RemoteBzrDir:
2058
if not isinstance(a_bzrdir, RemoteBzrDir):
2059
return self._vfs_initialize(a_bzrdir)
2060
medium = a_bzrdir._client._medium
2061
if medium._is_remote_before((1, 13)):
2062
return self._vfs_initialize(a_bzrdir)
2063
# Creating on a remote bzr dir.
2064
# 2) try direct creation via RPC
2065
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2066
verb = 'BzrDir.create_branch'
2068
response = a_bzrdir._call(verb, path, network_name)
2069
except errors.UnknownSmartMethod:
2070
# Fallback - use vfs methods
2071
medium._remember_remote_is_before((1, 13))
2072
return self._vfs_initialize(a_bzrdir)
2073
if response[0] != 'ok':
2074
raise errors.UnexpectedSmartServerResponse(response)
2075
# Turn the response into a RemoteRepository object.
2076
format = RemoteBranchFormat(network_name=response[1])
2077
repo_format = response_tuple_to_repo_format(response[3:])
2078
if response[2] == '':
2079
repo_bzrdir = a_bzrdir
2081
repo_bzrdir = RemoteBzrDir(
2082
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2084
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2085
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2086
format=format, setup_stacking=False)
2087
# XXX: We know this is a new branch, so it must have revno 0, revid
2088
# NULL_REVISION. Creating the branch locked would make this be unable
2089
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2090
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2091
return remote_branch
2093
def make_tags(self, branch):
2095
return self._custom_format.make_tags(branch)
2097
def supports_tags(self):
2098
# Remote branches might support tags, but we won't know until we
2099
# access the real remote branch.
2101
return self._custom_format.supports_tags()
2103
def supports_stacking(self):
2105
return self._custom_format.supports_stacking()
2107
def supports_set_append_revisions_only(self):
2109
return self._custom_format.supports_set_append_revisions_only()
2112
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
744
assert isinstance(a_bzrdir, RemoteBzrDir)
745
return a_bzrdir.create_branch()
748
class RemoteBranch(branch.Branch):
2113
749
"""Branch stored on a server accessed by HPSS RPC.
2115
751
At the moment most operations are mapped down to simple file operations.
2118
754
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2119
_client=None, format=None, setup_stacking=True):
2120
756
"""Create a RemoteBranch instance.
2122
758
:param real_branch: An optional local implementation of the branch
2123
759
format, usually accessing the data via the VFS.
2124
760
:param _client: Private parameter for testing.
2125
:param format: A RemoteBranchFormat object, None to create one
2126
automatically. If supplied it should have a network_name already
2128
:param setup_stacking: If True make an RPC call to determine the
2129
stacked (or not) status of the branch. If False assume the branch
2132
762
# We intentionally don't call the parent class's __init__, because it
2133
763
# will try to assign to self.tags, which is a property in this subclass.
2134
764
# And the parent's __init__ doesn't do much anyway.
765
self._revision_history_cache = None
2135
766
self.bzrdir = remote_bzrdir
2136
767
if _client is not None:
2137
768
self._client = _client
2139
self._client = remote_bzrdir._client
770
self._client = client._SmartClient(self.bzrdir._medium)
2140
771
self.repository = remote_repository
2141
772
if real_branch is not None:
2142
773
self._real_branch = real_branch
2218
801
Used before calls to self._real_branch.
2220
if self._real_branch is None:
2221
if not vfs.vfs_enabled():
2222
raise AssertionError('smart server vfs must be enabled '
2223
'to use vfs implementation')
803
if not self._real_branch:
804
assert vfs.vfs_enabled()
2224
805
self.bzrdir._ensure_real()
2225
self._real_branch = self.bzrdir._real_bzrdir.open_branch(
2226
ignore_fallbacks=self._real_ignore_fallbacks)
2227
if self.repository._real_repository is None:
2228
# Give the remote repository the matching real repo.
2229
real_repo = self._real_branch.repository
2230
if isinstance(real_repo, RemoteRepository):
2231
real_repo._ensure_real()
2232
real_repo = real_repo._real_repository
2233
self.repository._set_real_repository(real_repo)
2234
# Give the real branch the remote repository to let fast-pathing
806
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
807
# Give the remote repository the matching real repo.
808
real_repo = self._real_branch.repository
809
if isinstance(real_repo, RemoteRepository):
810
real_repo._ensure_real()
811
real_repo = real_repo._real_repository
812
self.repository._set_real_repository(real_repo)
813
# Give the branch the remote repository to let fast-pathing happen.
2236
814
self._real_branch.repository = self.repository
815
# XXX: deal with _lock_mode == 'w'
2237
816
if self._lock_mode == 'r':
2238
817
self._real_branch.lock_read()
2239
elif self._lock_mode == 'w':
2240
self._real_branch.lock_write(token=self._lock_token)
2242
def _translate_error(self, err, **context):
2243
self.repository._translate_error(err, branch=self, **context)
2245
def _clear_cached_state(self):
2246
super(RemoteBranch, self)._clear_cached_state()
2247
if self._real_branch is not None:
2248
self._real_branch._clear_cached_state()
2250
def _clear_cached_state_of_remote_branch_only(self):
2251
"""Like _clear_cached_state, but doesn't clear the cache of
2254
This is useful when falling back to calling a method of
2255
self._real_branch that changes state. In that case the underlying
2256
branch changes, so we need to invalidate this RemoteBranch's cache of
2257
it. However, there's no need to invalidate the _real_branch's cache
2258
too, in fact doing so might harm performance.
2260
super(RemoteBranch, self)._clear_cached_state()
2263
820
def control_files(self):
2278
835
self._ensure_real()
2279
836
return self._real_branch.get_physical_lock_status()
2281
def get_stacked_on_url(self):
2282
"""Get the URL this branch is stacked against.
2284
:raises NotStacked: If the branch is not stacked.
2285
:raises UnstackableBranchFormat: If the branch does not support
2287
:raises UnstackableRepositoryFormat: If the repository does not support
2291
# there may not be a repository yet, so we can't use
2292
# self._translate_error, so we can't use self._call either.
2293
response = self._client.call('Branch.get_stacked_on_url',
2294
self._remote_path())
2295
except errors.ErrorFromSmartServer, err:
2296
# there may not be a repository yet, so we can't call through
2297
# its _translate_error
2298
_translate_error(err, branch=self)
2299
except errors.UnknownSmartMethod, err:
2301
return self._real_branch.get_stacked_on_url()
2302
if response[0] != 'ok':
2303
raise errors.UnexpectedSmartServerResponse(response)
2306
def set_stacked_on_url(self, url):
2307
branch.Branch.set_stacked_on_url(self, url)
2309
self._is_stacked = False
2311
self._is_stacked = True
2313
def _vfs_get_tags_bytes(self):
2315
return self._real_branch._get_tags_bytes()
2317
def _get_tags_bytes(self):
2318
medium = self._client._medium
2319
if medium._is_remote_before((1, 13)):
2320
return self._vfs_get_tags_bytes()
2322
response = self._call('Branch.get_tags_bytes', self._remote_path())
2323
except errors.UnknownSmartMethod:
2324
medium._remember_remote_is_before((1, 13))
2325
return self._vfs_get_tags_bytes()
2328
def _vfs_set_tags_bytes(self, bytes):
2330
return self._real_branch._set_tags_bytes(bytes)
2332
def _set_tags_bytes(self, bytes):
2333
medium = self._client._medium
2334
if medium._is_remote_before((1, 18)):
2335
self._vfs_set_tags_bytes(bytes)
2339
self._remote_path(), self._lock_token, self._repo_lock_token)
2340
response = self._call_with_body_bytes(
2341
'Branch.set_tags_bytes', args, bytes)
2342
except errors.UnknownSmartMethod:
2343
medium._remember_remote_is_before((1, 18))
2344
self._vfs_set_tags_bytes(bytes)
2346
838
def lock_read(self):
2347
self.repository.lock_read()
2348
839
if not self._lock_mode:
2349
self._note_lock('r')
2350
840
self._lock_mode = 'r'
2351
841
self._lock_count = 1
2352
842
if self._real_branch is not None:
2361
851
branch_token = token
2362
852
repo_token = self.repository.lock_write()
2363
853
self.repository.unlock()
2364
err_context = {'token': token}
2365
response = self._call(
2366
'Branch.lock_write', self._remote_path(), branch_token,
2367
repo_token or '', **err_context)
2368
if response[0] != 'ok':
854
path = self.bzrdir._path_for_remote_call(self._client)
855
response = self._client.call('Branch.lock_write', path, branch_token,
857
if response[0] == 'ok':
858
ok, branch_token, repo_token = response
859
return branch_token, repo_token
860
elif response[0] == 'LockContention':
861
raise errors.LockContention('(remote lock)')
862
elif response[0] == 'TokenMismatch':
863
raise errors.TokenMismatch(token, '(remote token)')
864
elif response[0] == 'UnlockableTransport':
865
raise errors.UnlockableTransport(self.bzrdir.root_transport)
866
elif response[0] == 'ReadOnlyError':
867
raise errors.ReadOnlyError(self)
2369
869
raise errors.UnexpectedSmartServerResponse(response)
2370
ok, branch_token, repo_token = response
2371
return branch_token, repo_token
2373
871
def lock_write(self, token=None):
2374
872
if not self._lock_mode:
2375
self._note_lock('w')
2376
# Lock the branch and repo in one remote call.
2377
873
remote_tokens = self._remote_lock_write(token)
2378
874
self._lock_token, self._repo_lock_token = remote_tokens
2379
if not self._lock_token:
2380
raise SmartProtocolError('Remote server did not return a token!')
2381
# Tell the self.repository object that it is locked.
2382
self.repository.lock_write(
2383
self._repo_lock_token, _skip_rpc=True)
875
assert self._lock_token, 'Remote server did not return a token!'
876
# TODO: We really, really, really don't want to call _ensure_real
877
# here, but it's the easiest way to ensure coherency between the
878
# state of the RemoteBranch and RemoteRepository objects and the
879
# physical locks. If we don't materialise the real objects here,
880
# then getting everything in the right state later is complex, so
881
# for now we just do it the lazy way.
882
# -- Andrew Bennetts, 2007-02-22.
2385
884
if self._real_branch is not None:
2386
self._real_branch.lock_write(token=self._lock_token)
885
self._real_branch.repository.lock_write(
886
token=self._repo_lock_token)
888
self._real_branch.lock_write(token=self._lock_token)
890
self._real_branch.repository.unlock()
2387
891
if token is not None:
2388
892
self._leave_lock = True
894
# XXX: this case seems to be unreachable; token cannot be None.
2390
895
self._leave_lock = False
2391
896
self._lock_mode = 'w'
2392
897
self._lock_count = 1
2394
899
raise errors.ReadOnlyTransaction
2396
901
if token is not None:
2397
# A token was given to lock_write, and we're relocking, so
2398
# check that the given token actually matches the one we
902
# A token was given to lock_write, and we're relocking, so check
903
# that the given token actually matches the one we already have.
2400
904
if token != self._lock_token:
2401
905
raise errors.TokenMismatch(token, self._lock_token)
2402
906
self._lock_count += 1
2403
# Re-lock the repository too.
2404
self.repository.lock_write(self._repo_lock_token)
2405
return self._lock_token or None
907
return self._lock_token
2407
909
def _unlock(self, branch_token, repo_token):
2408
err_context = {'token': str((branch_token, repo_token))}
2409
response = self._call(
2410
'Branch.unlock', self._remote_path(), branch_token,
2411
repo_token or '', **err_context)
910
path = self.bzrdir._path_for_remote_call(self._client)
911
response = self._client.call('Branch.unlock', path, branch_token,
2412
913
if response == ('ok',):
2414
raise errors.UnexpectedSmartServerResponse(response)
915
elif response[0] == 'TokenMismatch':
916
raise errors.TokenMismatch(
917
str((branch_token, repo_token)), '(remote tokens)')
919
raise errors.UnexpectedSmartServerResponse(response)
2416
@only_raises(errors.LockNotHeld, errors.LockBroken)
2417
921
def unlock(self):
2419
self._lock_count -= 1
2420
if not self._lock_count:
2421
self._clear_cached_state()
2422
mode = self._lock_mode
2423
self._lock_mode = None
2424
if self._real_branch is not None:
2425
if (not self._leave_lock and mode == 'w' and
2426
self._repo_lock_token):
2427
# If this RemoteBranch will remove the physical lock
2428
# for the repository, make sure the _real_branch
2429
# doesn't do it first. (Because the _real_branch's
2430
# repository is set to be the RemoteRepository.)
2431
self._real_branch.repository.leave_lock_in_place()
2432
self._real_branch.unlock()
2434
# Only write-locked branched need to make a remote method
2435
# call to perform the unlock.
2437
if not self._lock_token:
2438
raise AssertionError('Locked, but no token!')
2439
branch_token = self._lock_token
2440
repo_token = self._repo_lock_token
2441
self._lock_token = None
2442
self._repo_lock_token = None
922
self._lock_count -= 1
923
if not self._lock_count:
924
self._clear_cached_state()
925
mode = self._lock_mode
926
self._lock_mode = None
927
if self._real_branch is not None:
2443
928
if not self._leave_lock:
2444
self._unlock(branch_token, repo_token)
2446
self.repository.unlock()
929
# If this RemoteBranch will remove the physical lock for the
930
# repository, make sure the _real_branch doesn't do it
931
# first. (Because the _real_branch's repository is set to
932
# be the RemoteRepository.)
933
self._real_branch.repository.leave_lock_in_place()
934
self._real_branch.unlock()
936
# Only write-locked branched need to make a remote method call
937
# to perfom the unlock.
939
assert self._lock_token, 'Locked, but no token!'
940
branch_token = self._lock_token
941
repo_token = self._repo_lock_token
942
self._lock_token = None
943
self._repo_lock_token = None
944
if not self._leave_lock:
945
self._unlock(branch_token, repo_token)
2448
947
def break_lock(self):
2449
948
self._ensure_real()
2450
949
return self._real_branch.break_lock()
2452
951
def leave_lock_in_place(self):
2453
if not self._lock_token:
2454
raise NotImplementedError(self.leave_lock_in_place)
2455
952
self._leave_lock = True
2457
954
def dont_leave_lock_in_place(self):
2458
if not self._lock_token:
2459
raise NotImplementedError(self.dont_leave_lock_in_place)
2460
955
self._leave_lock = False
2463
def get_rev_id(self, revno, history=None):
2465
return _mod_revision.NULL_REVISION
2466
last_revision_info = self.last_revision_info()
2467
ok, result = self.repository.get_rev_id_for_revno(
2468
revno, last_revision_info)
2471
missing_parent = result[1]
2472
# Either the revision named by the server is missing, or its parent
2473
# is. Call get_parent_map to determine which, so that we report a
2475
parent_map = self.repository.get_parent_map([missing_parent])
2476
if missing_parent in parent_map:
2477
missing_parent = parent_map[missing_parent]
2478
raise errors.RevisionNotPresent(missing_parent, self.repository)
2480
def _last_revision_info(self):
2481
response = self._call('Branch.last_revision_info', self._remote_path())
2482
if response[0] != 'ok':
2483
raise SmartProtocolError('unexpected response code %s' % (response,))
957
def last_revision_info(self):
958
"""See Branch.last_revision_info()."""
959
path = self.bzrdir._path_for_remote_call(self._client)
960
response = self._client.call('Branch.last_revision_info', path)
961
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
2484
962
revno = int(response[1])
2485
963
last_revision = response[2]
2486
964
return (revno, last_revision)
2488
966
def _gen_revision_history(self):
2489
967
"""See Branch._gen_revision_history()."""
2490
if self._is_stacked:
2492
return self._real_branch._gen_revision_history()
2493
response_tuple, response_handler = self._call_expecting_body(
2494
'Branch.revision_history', self._remote_path())
2495
if response_tuple[0] != 'ok':
2496
raise errors.UnexpectedSmartServerResponse(response_tuple)
2497
result = response_handler.read_body_bytes().split('\x00')
968
path = self.bzrdir._path_for_remote_call(self._client)
969
response = self._client.call_expecting_body(
970
'Branch.revision_history', path)
971
assert response[0][0] == 'ok', ('unexpected response code %s'
973
result = response[1].read_body_bytes().split('\x00')
2498
974
if result == ['']:
2502
def _remote_path(self):
2503
return self.bzrdir._path_for_remote_call(self._client)
2505
def _set_last_revision_descendant(self, revision_id, other_branch,
2506
allow_diverged=False, allow_overwrite_descendant=False):
2507
# This performs additional work to meet the hook contract; while its
2508
# undesirable, we have to synthesise the revno to call the hook, and
2509
# not calling the hook is worse as it means changes can't be prevented.
2510
# Having calculated this though, we can't just call into
2511
# set_last_revision_info as a simple call, because there is a set_rh
2512
# hook that some folk may still be using.
2513
old_revno, old_revid = self.last_revision_info()
2514
history = self._lefthand_history(revision_id)
2515
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2516
err_context = {'other_branch': other_branch}
2517
response = self._call('Branch.set_last_revision_ex',
2518
self._remote_path(), self._lock_token, self._repo_lock_token,
2519
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2521
self._clear_cached_state()
2522
if len(response) != 3 and response[0] != 'ok':
2523
raise errors.UnexpectedSmartServerResponse(response)
2524
new_revno, new_revision_id = response[1:]
2525
self._last_revision_info_cache = new_revno, new_revision_id
2526
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2527
if self._real_branch is not None:
2528
cache = new_revno, new_revision_id
2529
self._real_branch._last_revision_info_cache = cache
2531
def _set_last_revision(self, revision_id):
2532
old_revno, old_revid = self.last_revision_info()
2533
# This performs additional work to meet the hook contract; while its
2534
# undesirable, we have to synthesise the revno to call the hook, and
2535
# not calling the hook is worse as it means changes can't be prevented.
2536
# Having calculated this though, we can't just call into
2537
# set_last_revision_info as a simple call, because there is a set_rh
2538
# hook that some folk may still be using.
2539
history = self._lefthand_history(revision_id)
2540
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2541
self._clear_cached_state()
2542
response = self._call('Branch.set_last_revision',
2543
self._remote_path(), self._lock_token, self._repo_lock_token,
2545
if response != ('ok',):
2546
raise errors.UnexpectedSmartServerResponse(response)
2547
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2549
978
@needs_write_lock
2550
979
def set_revision_history(self, rev_history):
2551
980
# Send just the tip revision of the history; the server will generate
2552
981
# the full history from that. If the revision doesn't exist in this
2553
982
# branch, NoSuchRevision will be raised.
983
path = self.bzrdir._path_for_remote_call(self._client)
2554
984
if rev_history == []:
2555
985
rev_id = 'null:'
2557
987
rev_id = rev_history[-1]
2558
self._set_last_revision(rev_id)
2559
for hook in branch.Branch.hooks['set_rh']:
2560
hook(self, rev_history)
988
self._clear_cached_state()
989
response = self._client.call('Branch.set_last_revision',
990
path, self._lock_token, self._repo_lock_token, rev_id)
991
if response[0] == 'NoSuchRevision':
992
raise NoSuchRevision(self, rev_id)
994
assert response == ('ok',), (
995
'unexpected response code %r' % (response,))
2561
996
self._cache_revision_history(rev_history)
2563
def _get_parent_location(self):
2564
medium = self._client._medium
2565
if medium._is_remote_before((1, 13)):
2566
return self._vfs_get_parent_location()
2568
response = self._call('Branch.get_parent', self._remote_path())
2569
except errors.UnknownSmartMethod:
2570
medium._remember_remote_is_before((1, 13))
2571
return self._vfs_get_parent_location()
2572
if len(response) != 1:
2573
raise errors.UnexpectedSmartServerResponse(response)
2574
parent_location = response[0]
2575
if parent_location == '':
2577
return parent_location
2579
def _vfs_get_parent_location(self):
2581
return self._real_branch._get_parent_location()
2583
def _set_parent_location(self, url):
2584
medium = self._client._medium
2585
if medium._is_remote_before((1, 15)):
2586
return self._vfs_set_parent_location(url)
2588
call_url = url or ''
2589
if type(call_url) is not str:
2590
raise AssertionError('url must be a str or None (%s)' % url)
2591
response = self._call('Branch.set_parent_location',
2592
self._remote_path(), self._lock_token, self._repo_lock_token,
2594
except errors.UnknownSmartMethod:
2595
medium._remember_remote_is_before((1, 15))
2596
return self._vfs_set_parent_location(url)
2598
raise errors.UnexpectedSmartServerResponse(response)
2600
def _vfs_set_parent_location(self, url):
2602
return self._real_branch._set_parent_location(url)
998
def get_parent(self):
1000
return self._real_branch.get_parent()
1002
def set_parent(self, url):
1004
return self._real_branch.set_parent(url)
1006
def get_config(self):
1007
return RemoteBranchConfig(self)
1009
def sprout(self, to_bzrdir, revision_id=None):
1010
# Like Branch.sprout, except that it sprouts a branch in the default
1011
# format, because RemoteBranches can't be created at arbitrary URLs.
1012
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1013
# to_bzrdir.create_branch...
1014
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1015
self.copy_content_into(result, revision_id=revision_id)
1016
result.set_parent(self.bzrdir.root_transport.base)
1020
def append_revision(self, *revision_ids):
1022
return self._real_branch.append_revision(*revision_ids)
2604
1024
@needs_write_lock
2605
1025
def pull(self, source, overwrite=False, stop_revision=None,
2607
self._clear_cached_state_of_remote_branch_only()
1027
# FIXME: This asks the real branch to run the hooks, which means
1028
# they're called with the wrong target branch parameter.
1029
# The test suite specifically allows this at present but it should be
1030
# fixed. It should get a _override_hook_target branch,
1031
# as push does. -- mbp 20070405
2608
1032
self._ensure_real()
2609
return self._real_branch.pull(
1033
self._real_branch.pull(
2610
1034
source, overwrite=overwrite, stop_revision=stop_revision,
2611
_override_hook_target=self, **kwargs)
2613
1037
@needs_read_lock
2614
1038
def push(self, target, overwrite=False, stop_revision=None):
2620
1044
def is_locked(self):
2621
1045
return self._lock_count >= 1
2624
def revision_id_to_revno(self, revision_id):
2626
return self._real_branch.revision_id_to_revno(revision_id)
2629
1047
def set_last_revision_info(self, revno, revision_id):
2630
# XXX: These should be returned by the set_last_revision_info verb
2631
old_revno, old_revid = self.last_revision_info()
2632
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2633
revision_id = ensure_null(revision_id)
2635
response = self._call('Branch.set_last_revision_info',
2636
self._remote_path(), self._lock_token, self._repo_lock_token,
2637
str(revno), revision_id)
2638
except errors.UnknownSmartMethod:
2640
self._clear_cached_state_of_remote_branch_only()
2641
self._real_branch.set_last_revision_info(revno, revision_id)
2642
self._last_revision_info_cache = revno, revision_id
2644
if response == ('ok',):
2645
self._clear_cached_state()
2646
self._last_revision_info_cache = revno, revision_id
2647
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2648
# Update the _real_branch's cache too.
2649
if self._real_branch is not None:
2650
cache = self._last_revision_info_cache
2651
self._real_branch._last_revision_info_cache = cache
2653
raise errors.UnexpectedSmartServerResponse(response)
1049
self._clear_cached_state()
1050
return self._real_branch.set_last_revision_info(revno, revision_id)
2656
1052
def generate_revision_history(self, revision_id, last_rev=None,
2657
1053
other_branch=None):
2658
medium = self._client._medium
2659
if not medium._is_remote_before((1, 6)):
2660
# Use a smart method for 1.6 and above servers
2662
self._set_last_revision_descendant(revision_id, other_branch,
2663
allow_diverged=True, allow_overwrite_descendant=True)
2665
except errors.UnknownSmartMethod:
2666
medium._remember_remote_is_before((1, 6))
2667
self._clear_cached_state_of_remote_branch_only()
2668
self.set_revision_history(self._lefthand_history(revision_id,
2669
last_rev=last_rev,other_branch=other_branch))
1055
return self._real_branch.generate_revision_history(
1056
revision_id, last_rev=last_rev, other_branch=other_branch)
1061
return self._real_branch.tags
2671
1063
def set_push_location(self, location):
2672
1064
self._ensure_real()
2673
1065
return self._real_branch.set_push_location(location)
2676
class RemoteConfig(object):
2677
"""A Config that reads and writes from smart verbs.
2679
It is a low-level object that considers config data to be name/value pairs
2680
that may be associated with a section. Assigning meaning to the these
2681
values is done at higher levels like bzrlib.config.TreeConfig.
2684
def get_option(self, name, section=None, default=None):
2685
"""Return the value associated with a named option.
2687
:param name: The name of the value
2688
:param section: The section the option is in (if any)
2689
:param default: The value to return if the value is not set
2690
:return: The value or default value
2693
configobj = self._get_configobj()
2695
section_obj = configobj
2698
section_obj = configobj[section]
2701
return section_obj.get(name, default)
2702
except errors.UnknownSmartMethod:
2703
return self._vfs_get_option(name, section, default)
2705
def _response_to_configobj(self, response):
2706
if len(response[0]) and response[0][0] != 'ok':
2707
raise errors.UnexpectedSmartServerResponse(response)
2708
lines = response[1].read_body_bytes().splitlines()
2709
return config.ConfigObj(lines, encoding='utf-8')
2712
class RemoteBranchConfig(RemoteConfig):
2713
"""A RemoteConfig for Branches."""
2715
def __init__(self, branch):
2716
self._branch = branch
2718
def _get_configobj(self):
2719
path = self._branch._remote_path()
2720
response = self._branch._client.call_expecting_body(
2721
'Branch.get_config_file', path)
2722
return self._response_to_configobj(response)
2724
def set_option(self, value, name, section=None):
2725
"""Set the value associated with a named option.
2727
:param value: The value to set
2728
:param name: The name of the value to set
2729
:param section: The section the option is in (if any)
2731
medium = self._branch._client._medium
2732
if medium._is_remote_before((1, 14)):
2733
return self._vfs_set_option(value, name, section)
2735
path = self._branch._remote_path()
2736
response = self._branch._client.call('Branch.set_config_option',
2737
path, self._branch._lock_token, self._branch._repo_lock_token,
2738
value.encode('utf8'), name, section or '')
2739
except errors.UnknownSmartMethod:
2740
medium._remember_remote_is_before((1, 14))
2741
return self._vfs_set_option(value, name, section)
2743
raise errors.UnexpectedSmartServerResponse(response)
2745
def _real_object(self):
2746
self._branch._ensure_real()
2747
return self._branch._real_branch
2749
def _vfs_set_option(self, value, name, section=None):
2750
return self._real_object()._get_config().set_option(
2751
value, name, section)
2754
class RemoteBzrDirConfig(RemoteConfig):
2755
"""A RemoteConfig for BzrDirs."""
2757
def __init__(self, bzrdir):
2758
self._bzrdir = bzrdir
2760
def _get_configobj(self):
2761
medium = self._bzrdir._client._medium
2762
verb = 'BzrDir.get_config_file'
2763
if medium._is_remote_before((1, 15)):
2764
raise errors.UnknownSmartMethod(verb)
2765
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2766
response = self._bzrdir._call_expecting_body(
2768
return self._response_to_configobj(response)
2770
def _vfs_get_option(self, name, section, default):
2771
return self._real_object()._get_config().get_option(
2772
name, section, default)
2774
def set_option(self, value, name, section=None):
2775
"""Set the value associated with a named option.
2777
:param value: The value to set
2778
:param name: The name of the value to set
2779
:param section: The section the option is in (if any)
2781
return self._real_object()._get_config().set_option(
2782
value, name, section)
2784
def _real_object(self):
2785
self._bzrdir._ensure_real()
2786
return self._bzrdir._real_bzrdir
1067
def update_revisions(self, other, stop_revision=None):
1069
return self._real_branch.update_revisions(
1070
other, stop_revision=stop_revision)
1073
class RemoteBranchConfig(BranchConfig):
1076
self.branch._ensure_real()
1077
return self.branch._real_branch.get_config().username()
1079
def _get_branch_data_config(self):
1080
self.branch._ensure_real()
1081
if self._branch_data_config is None:
1082
self._branch_data_config = TreeConfig(self.branch._real_branch)
1083
return self._branch_data_config
2790
1086
def _extract_tar(tar, to_dir):
2795
1091
for tarinfo in tar:
2796
1092
tar.extract(tarinfo, to_dir)
2799
def _translate_error(err, **context):
2800
"""Translate an ErrorFromSmartServer into a more useful error.
2802
Possible context keys:
2810
If the error from the server doesn't match a known pattern, then
2811
UnknownErrorFromSmartServer is raised.
2815
return context[name]
2816
except KeyError, key_err:
2817
mutter('Missing key %r in context %r', key_err.args[0], context)
2820
"""Get the path from the context if present, otherwise use first error
2824
return context['path']
2825
except KeyError, key_err:
2827
return err.error_args[0]
2828
except IndexError, idx_err:
2830
'Missing key %r in context %r', key_err.args[0], context)
2833
if err.error_verb == 'IncompatibleRepositories':
2834
raise errors.IncompatibleRepositories(err.error_args[0],
2835
err.error_args[1], err.error_args[2])
2836
elif err.error_verb == 'NoSuchRevision':
2837
raise NoSuchRevision(find('branch'), err.error_args[0])
2838
elif err.error_verb == 'nosuchrevision':
2839
raise NoSuchRevision(find('repository'), err.error_args[0])
2840
elif err.error_verb == 'nobranch':
2841
if len(err.error_args) >= 1:
2842
extra = err.error_args[0]
2845
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2847
elif err.error_verb == 'norepository':
2848
raise errors.NoRepositoryPresent(find('bzrdir'))
2849
elif err.error_verb == 'LockContention':
2850
raise errors.LockContention('(remote lock)')
2851
elif err.error_verb == 'UnlockableTransport':
2852
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2853
elif err.error_verb == 'LockFailed':
2854
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2855
elif err.error_verb == 'TokenMismatch':
2856
raise errors.TokenMismatch(find('token'), '(remote token)')
2857
elif err.error_verb == 'Diverged':
2858
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2859
elif err.error_verb == 'TipChangeRejected':
2860
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2861
elif err.error_verb == 'UnstackableBranchFormat':
2862
raise errors.UnstackableBranchFormat(*err.error_args)
2863
elif err.error_verb == 'UnstackableRepositoryFormat':
2864
raise errors.UnstackableRepositoryFormat(*err.error_args)
2865
elif err.error_verb == 'NotStacked':
2866
raise errors.NotStacked(branch=find('branch'))
2867
elif err.error_verb == 'PermissionDenied':
2869
if len(err.error_args) >= 2:
2870
extra = err.error_args[1]
2873
raise errors.PermissionDenied(path, extra=extra)
2874
elif err.error_verb == 'ReadError':
2876
raise errors.ReadError(path)
2877
elif err.error_verb == 'NoSuchFile':
2879
raise errors.NoSuchFile(path)
2880
elif err.error_verb == 'FileExists':
2881
raise errors.FileExists(err.error_args[0])
2882
elif err.error_verb == 'DirectoryNotEmpty':
2883
raise errors.DirectoryNotEmpty(err.error_args[0])
2884
elif err.error_verb == 'ShortReadvError':
2885
args = err.error_args
2886
raise errors.ShortReadvError(
2887
args[0], int(args[1]), int(args[2]), int(args[3]))
2888
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2889
encoding = str(err.error_args[0]) # encoding must always be a string
2890
val = err.error_args[1]
2891
start = int(err.error_args[2])
2892
end = int(err.error_args[3])
2893
reason = str(err.error_args[4]) # reason must always be a string
2894
if val.startswith('u:'):
2895
val = val[2:].decode('utf-8')
2896
elif val.startswith('s:'):
2897
val = val[2:].decode('base64')
2898
if err.error_verb == 'UnicodeDecodeError':
2899
raise UnicodeDecodeError(encoding, val, start, end, reason)
2900
elif err.error_verb == 'UnicodeEncodeError':
2901
raise UnicodeEncodeError(encoding, val, start, end, reason)
2902
elif err.error_verb == 'ReadOnlyError':
2903
raise errors.TransportNotPossible('readonly transport')
2904
raise errors.UnknownErrorFromSmartServer(err)