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
repository as _mod_repository,
33
revision as _mod_revision,
37
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
28
from bzrlib.branch import Branch, BranchReferenceFormat
38
29
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
39
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
40
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
44
33
from bzrlib.lockable_files import LockableFiles
45
from bzrlib.smart import client, vfs, repository as smart_repo
46
from bzrlib.revision import ensure_null, NULL_REVISION
47
from bzrlib.repository import RepositoryWriteLockResult
48
from bzrlib.trace import mutter, note, warning
51
class _RpcHelper(object):
52
"""Mixin class that helps with issuing RPCs."""
54
def _call(self, method, *args, **err_context):
56
return self._client.call(method, *args)
57
except errors.ErrorFromSmartServer, err:
58
self._translate_error(err, **err_context)
60
def _call_expecting_body(self, method, *args, **err_context):
62
return self._client.call_expecting_body(method, *args)
63
except errors.ErrorFromSmartServer, err:
64
self._translate_error(err, **err_context)
66
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
68
return self._client.call_with_body_bytes(method, args, body_bytes)
69
except errors.ErrorFromSmartServer, err:
70
self._translate_error(err, **err_context)
72
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
75
return self._client.call_with_body_bytes_expecting_body(
76
method, args, body_bytes)
77
except errors.ErrorFromSmartServer, err:
78
self._translate_error(err, **err_context)
81
def response_tuple_to_repo_format(response):
82
"""Convert a response tuple describing a repository format to a format."""
83
format = RemoteRepositoryFormat()
84
format._rich_root_data = (response[0] == 'yes')
85
format._supports_tree_reference = (response[1] == 'yes')
86
format._supports_external_lookups = (response[2] == 'yes')
87
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
91
38
# Note: RemoteBzrDirFormat is in bzrdir.py
93
class RemoteBzrDir(BzrDir, _RpcHelper):
40
class RemoteBzrDir(BzrDir):
94
41
"""Control directory on a remote server, accessed via bzr:// or similar."""
96
def __init__(self, transport, format, _client=None, _force_probe=False):
43
def __init__(self, transport, _client=None):
97
44
"""Construct a RemoteBzrDir.
99
46
:param _client: Private parameter for testing. Disables probing and the
100
47
use of a real bzrdir.
102
BzrDir.__init__(self, transport, format)
49
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
103
50
# this object holds a delegated bzrdir that uses file-level operations
104
51
# to talk to the other side
105
52
self._real_bzrdir = None
106
self._has_working_tree = None
107
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
108
# create_branch for details.
109
self._next_open_branch_result = None
111
54
if _client is None:
112
medium = transport.get_smart_medium()
113
self._client = client._SmartClient(medium)
55
self._shared_medium = transport.get_shared_medium()
56
self._client = client._SmartClient(self._shared_medium)
115
58
self._client = _client
122
return '%s(%r)' % (self.__class__.__name__, self._client)
124
def _probe_bzrdir(self):
125
medium = self._client._medium
59
self._shared_medium = None
126
62
path = self._path_for_remote_call(self._client)
127
if medium._is_remote_before((2, 1)):
131
self._rpc_open_2_1(path)
133
except errors.UnknownSmartMethod:
134
medium._remember_remote_is_before((2, 1))
137
def _rpc_open_2_1(self, path):
138
response = self._call('BzrDir.open_2.1', path)
139
if response == ('no',):
140
raise errors.NotBranchError(path=self.root_transport.base)
141
elif response[0] == 'yes':
142
if response[1] == 'yes':
143
self._has_working_tree = True
144
elif response[1] == 'no':
145
self._has_working_tree = False
147
raise errors.UnexpectedSmartServerResponse(response)
149
raise errors.UnexpectedSmartServerResponse(response)
151
def _rpc_open(self, path):
152
response = self._call('BzrDir.open', path)
63
response = self._client.call('BzrDir.open', path)
153
64
if response not in [('yes',), ('no',)]:
154
65
raise errors.UnexpectedSmartServerResponse(response)
155
66
if response == ('no',):
156
raise errors.NotBranchError(path=self.root_transport.base)
67
raise errors.NotBranchError(path=transport.base)
158
69
def _ensure_real(self):
159
70
"""Ensure that there is a _real_bzrdir set.
161
72
Used before calls to self._real_bzrdir.
163
74
if not self._real_bzrdir:
164
if 'hpssvfs' in debug.debug_flags:
166
warning('VFS BzrDir access triggered\n%s',
167
''.join(traceback.format_stack()))
168
75
self._real_bzrdir = BzrDir.open_from_transport(
169
76
self.root_transport, _server_formats=False)
170
self._format._network_name = \
171
self._real_bzrdir._format.network_name()
173
def _translate_error(self, err, **context):
174
_translate_error(err, bzrdir=self, **context)
176
def break_lock(self):
177
# Prevent aliasing problems in the next_open_branch_result cache.
178
# See create_branch for rationale.
179
self._next_open_branch_result = None
180
return BzrDir.break_lock(self)
182
def _vfs_cloning_metadir(self, require_stacking=False):
184
return self._real_bzrdir.cloning_metadir(
185
require_stacking=require_stacking)
187
def cloning_metadir(self, require_stacking=False):
188
medium = self._client._medium
189
if medium._is_remote_before((1, 13)):
190
return self._vfs_cloning_metadir(require_stacking=require_stacking)
191
verb = 'BzrDir.cloning_metadir'
196
path = self._path_for_remote_call(self._client)
198
response = self._call(verb, path, stacking)
199
except errors.UnknownSmartMethod:
200
medium._remember_remote_is_before((1, 13))
201
return self._vfs_cloning_metadir(require_stacking=require_stacking)
202
except errors.UnknownErrorFromSmartServer, err:
203
if err.error_tuple != ('BranchReference',):
205
# We need to resolve the branch reference to determine the
206
# cloning_metadir. This causes unnecessary RPCs to open the
207
# referenced branch (and bzrdir, etc) but only when the caller
208
# didn't already resolve the branch reference.
209
referenced_branch = self.open_branch()
210
return referenced_branch.bzrdir.cloning_metadir()
211
if len(response) != 3:
212
raise errors.UnexpectedSmartServerResponse(response)
213
control_name, repo_name, branch_info = response
214
if len(branch_info) != 2:
215
raise errors.UnexpectedSmartServerResponse(response)
216
branch_ref, branch_name = branch_info
217
format = controldir.network_format_registry.get(control_name)
219
format.repository_format = repository.network_format_registry.get(
221
if branch_ref == 'ref':
222
# XXX: we need possible_transports here to avoid reopening the
223
# connection to the referenced location
224
ref_bzrdir = BzrDir.open(branch_name)
225
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
226
format.set_branch_format(branch_format)
227
elif branch_ref == 'branch':
229
format.set_branch_format(
230
branch.network_format_registry.get(branch_name))
232
raise errors.UnexpectedSmartServerResponse(response)
235
78
def create_repository(self, shared=False):
236
# as per meta1 formats - just delegate to the format object which may
238
result = self._format.repository_format.initialize(self, shared)
239
if not isinstance(result, RemoteRepository):
240
return self.open_repository()
244
def destroy_repository(self):
245
"""See BzrDir.destroy_repository"""
247
self._real_bzrdir.destroy_repository()
249
def create_branch(self, name=None):
250
# as per meta1 formats - just delegate to the format object which may
252
real_branch = self._format.get_branch_format().initialize(self,
254
if not isinstance(real_branch, RemoteBranch):
255
result = RemoteBranch(self, self.find_repository(), real_branch,
259
# BzrDir.clone_on_transport() uses the result of create_branch but does
260
# not return it to its callers; we save approximately 8% of our round
261
# trips by handing the branch we created back to the first caller to
262
# open_branch rather than probing anew. Long term we need a API in
263
# bzrdir that doesn't discard result objects (like result_branch).
265
self._next_open_branch_result = result
268
def destroy_branch(self, name=None):
269
"""See BzrDir.destroy_branch"""
271
self._real_bzrdir.destroy_branch(name=name)
272
self._next_open_branch_result = None
274
def create_workingtree(self, revision_id=None, from_branch=None):
80
self._real_bzrdir.create_repository(shared=shared)
81
return self.open_repository()
83
def create_branch(self):
85
real_branch = self._real_bzrdir.create_branch()
86
return RemoteBranch(self, self.find_repository(), real_branch)
88
def create_workingtree(self, revision_id=None):
275
89
raise errors.NotLocalUrl(self.transport.base)
277
def find_branch_format(self, name=None):
91
def find_branch_format(self):
278
92
"""Find the branch 'format' for this bzrdir.
280
94
This might be a synthetic object for e.g. RemoteBranch and SVN.
282
b = self.open_branch(name=name)
96
b = self.open_branch()
285
def get_branch_reference(self, name=None):
99
def get_branch_reference(self):
286
100
"""See BzrDir.get_branch_reference()."""
288
# XXX JRV20100304: Support opening colocated branches
289
raise errors.NoColocatedBranchSupport(self)
290
response = self._get_branch_reference()
291
if response[0] == 'ref':
296
def _get_branch_reference(self):
297
101
path = self._path_for_remote_call(self._client)
298
medium = self._client._medium
300
('BzrDir.open_branchV3', (2, 1)),
301
('BzrDir.open_branchV2', (1, 13)),
302
('BzrDir.open_branch', None),
304
for verb, required_version in candidate_calls:
305
if required_version and medium._is_remote_before(required_version):
308
response = self._call(verb, path)
309
except errors.UnknownSmartMethod:
310
if required_version is None:
312
medium._remember_remote_is_before(required_version)
315
if verb == 'BzrDir.open_branch':
316
if response[0] != 'ok':
317
raise errors.UnexpectedSmartServerResponse(response)
318
if response[1] != '':
319
return ('ref', response[1])
321
return ('branch', '')
322
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)
323
113
raise errors.UnexpectedSmartServerResponse(response)
326
def _get_tree_branch(self, name=None):
327
"""See BzrDir._get_tree_branch()."""
328
return None, self.open_branch(name=name)
330
def open_branch(self, name=None, unsupported=False,
331
ignore_fallbacks=False):
333
raise NotImplementedError('unsupported flag support not implemented yet.')
334
if self._next_open_branch_result is not None:
335
# See create_branch for details.
336
result = self._next_open_branch_result
337
self._next_open_branch_result = None
339
response = self._get_branch_reference()
340
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())
341
122
# a branch reference, use the existing BranchReference logic.
342
123
format = BranchReferenceFormat()
343
return format.open(self, name=name, _found=True,
344
location=response[1], ignore_fallbacks=ignore_fallbacks)
345
branch_format_name = response[1]
346
if not branch_format_name:
347
branch_format_name = None
348
format = RemoteBranchFormat(network_name=branch_format_name)
349
return RemoteBranch(self, self.find_repository(), format=format,
350
setup_stacking=not ignore_fallbacks, name=name)
352
def _open_repo_v1(self, path):
353
verb = 'BzrDir.find_repository'
354
response = self._call(verb, path)
355
if response[0] != 'ok':
356
raise errors.UnexpectedSmartServerResponse(response)
357
# servers that only support the v1 method don't support external
360
repo = self._real_bzrdir.open_repository()
361
response = response + ('no', repo._format.network_name())
362
return response, repo
364
def _open_repo_v2(self, path):
365
verb = 'BzrDir.find_repositoryV2'
366
response = self._call(verb, path)
367
if response[0] != 'ok':
368
raise errors.UnexpectedSmartServerResponse(response)
370
repo = self._real_bzrdir.open_repository()
371
response = response + (repo._format.network_name(),)
372
return response, repo
374
def _open_repo_v3(self, path):
375
verb = 'BzrDir.find_repositoryV3'
376
medium = self._client._medium
377
if medium._is_remote_before((1, 13)):
378
raise errors.UnknownSmartMethod(verb)
380
response = self._call(verb, path)
381
except errors.UnknownSmartMethod:
382
medium._remember_remote_is_before((1, 13))
384
if response[0] != 'ok':
385
raise errors.UnexpectedSmartServerResponse(response)
386
return response, None
124
return format.open(self, _found=True, location=reference_url)
388
126
def open_repository(self):
389
127
path = self._path_for_remote_call(self._client)
391
for probe in [self._open_repo_v3, self._open_repo_v2,
394
response, real_repo = probe(path)
396
except errors.UnknownSmartMethod:
399
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
400
if response[0] != 'ok':
401
raise errors.UnexpectedSmartServerResponse(response)
402
if len(response) != 6:
403
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,)
404
134
if response[1] == '':
405
# repo is at this dir.
406
format = response_tuple_to_repo_format(response[2:])
407
# Used to support creating a real format instance when needed.
408
format._creating_bzrdir = self
409
remote_repo = RemoteRepository(self, format)
410
format._creating_repo = remote_repo
411
if real_repo is not None:
412
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)
415
140
raise errors.NoRepositoryPresent(self)
417
def has_workingtree(self):
418
if self._has_working_tree is None:
420
self._has_working_tree = self._real_bzrdir.has_workingtree()
421
return self._has_working_tree
423
142
def open_workingtree(self, recommend_upgrade=True):
424
if self.has_workingtree():
144
if self._real_bzrdir.has_workingtree():
425
145
raise errors.NotLocalUrl(self.root_transport)
427
147
raise errors.NoWorkingTree(self.root_transport.base)
469
182
Instances of this repository are represented by RemoteRepository
472
The RemoteRepositoryFormat is parameterized during construction
185
The RemoteRepositoryFormat is parameterised during construction
473
186
to reflect the capabilities of the real, remote format. Specifically
474
187
the attributes rich_root_data and supports_tree_reference are set
475
188
on a per instance basis, and are not set (and should not be) at
478
:ivar _custom_format: If set, a specific concrete repository format that
479
will be used when initializing a repository with this
480
RemoteRepositoryFormat.
481
:ivar _creating_repo: If set, the repository object that this
482
RemoteRepositoryFormat was created for: it can be called into
483
to obtain data like the network name.
486
_matchingbzrdir = RemoteBzrDirFormat()
489
repository.RepositoryFormat.__init__(self)
490
self._custom_format = None
491
self._network_name = None
492
self._creating_bzrdir = None
493
self._supports_chks = None
494
self._supports_external_lookups = None
495
self._supports_tree_reference = None
496
self._rich_root_data = None
499
return "%s(_network_name=%r)" % (self.__class__.__name__,
503
def fast_deltas(self):
505
return self._custom_format.fast_deltas
508
def rich_root_data(self):
509
if self._rich_root_data is None:
511
self._rich_root_data = self._custom_format.rich_root_data
512
return self._rich_root_data
515
def supports_chks(self):
516
if self._supports_chks is None:
518
self._supports_chks = self._custom_format.supports_chks
519
return self._supports_chks
522
def supports_external_lookups(self):
523
if self._supports_external_lookups is None:
525
self._supports_external_lookups = \
526
self._custom_format.supports_external_lookups
527
return self._supports_external_lookups
530
def supports_tree_reference(self):
531
if self._supports_tree_reference is None:
533
self._supports_tree_reference = \
534
self._custom_format.supports_tree_reference
535
return self._supports_tree_reference
537
def _vfs_initialize(self, a_bzrdir, shared):
538
"""Helper for common code in initialize."""
539
if self._custom_format:
540
# Custom format requested
541
result = self._custom_format.initialize(a_bzrdir, shared=shared)
542
elif self._creating_bzrdir is not None:
543
# Use the format that the repository we were created to back
545
prior_repo = self._creating_bzrdir.open_repository()
546
prior_repo._ensure_real()
547
result = prior_repo._real_repository._format.initialize(
548
a_bzrdir, shared=shared)
550
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
551
# support remote initialization.
552
# We delegate to a real object at this point (as RemoteBzrDir
553
# delegate to the repository format which would lead to infinite
554
# recursion if we just called a_bzrdir.create_repository.
555
a_bzrdir._ensure_real()
556
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
557
if not isinstance(result, RemoteRepository):
558
return self.open(a_bzrdir)
192
_matchingbzrdir = RemoteBzrDirFormat
562
194
def initialize(self, a_bzrdir, shared=False):
563
# Being asked to create on a non RemoteBzrDir:
564
if not isinstance(a_bzrdir, RemoteBzrDir):
565
return self._vfs_initialize(a_bzrdir, shared)
566
medium = a_bzrdir._client._medium
567
if medium._is_remote_before((1, 13)):
568
return self._vfs_initialize(a_bzrdir, shared)
569
# Creating on a remote bzr dir.
570
# 1) get the network name to use.
571
if self._custom_format:
572
network_name = self._custom_format.network_name()
573
elif self._network_name:
574
network_name = self._network_name
576
# Select the current bzrlib default and ask for that.
577
reference_bzrdir_format = bzrdir.format_registry.get('default')()
578
reference_format = reference_bzrdir_format.repository_format
579
network_name = reference_format.network_name()
580
# 2) try direct creation via RPC
581
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
582
verb = 'BzrDir.create_repository'
588
response = a_bzrdir._call(verb, path, network_name, shared_str)
589
except errors.UnknownSmartMethod:
590
# Fallback - use vfs methods
591
medium._remember_remote_is_before((1, 13))
592
return self._vfs_initialize(a_bzrdir, shared)
594
# Turn the response into a RemoteRepository object.
595
format = response_tuple_to_repo_format(response[1:])
596
# Used to support creating a real format instance when needed.
597
format._creating_bzrdir = a_bzrdir
598
remote_repo = RemoteRepository(a_bzrdir, format)
599
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)
602
199
def open(self, a_bzrdir):
603
if not isinstance(a_bzrdir, RemoteBzrDir):
604
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
200
assert isinstance(a_bzrdir, RemoteBzrDir)
605
201
return a_bzrdir.open_repository()
607
def _ensure_real(self):
608
if self._custom_format is None:
609
self._custom_format = repository.network_format_registry.get(
613
def _fetch_order(self):
615
return self._custom_format._fetch_order
618
def _fetch_uses_deltas(self):
620
return self._custom_format._fetch_uses_deltas
623
def _fetch_reconcile(self):
625
return self._custom_format._fetch_reconcile
627
203
def get_format_description(self):
629
return 'Remote: ' + self._custom_format.get_format_description()
204
return 'bzr remote repository'
631
206
def __eq__(self, other):
632
return self.__class__ is other.__class__
634
def network_name(self):
635
if self._network_name:
636
return self._network_name
637
self._creating_repo._ensure_real()
638
return self._creating_repo._real_repository._format.network_name()
641
def pack_compresses(self):
643
return self._custom_format.pack_compresses
646
def _serializer(self):
648
return self._custom_format._serializer
651
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
652
controldir.ControlComponent):
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):
653
220
"""Repository accessed over rpc.
655
222
For the moment most operations are performed using local transport-backed
749
271
self._ensure_real()
750
272
return self._real_repository.commit_write_group()
752
def resume_write_group(self, tokens):
754
return self._real_repository.resume_write_group(tokens)
756
def suspend_write_group(self):
758
return self._real_repository.suspend_write_group()
760
def get_missing_parent_inventories(self, check_for_missing_texts=True):
762
return self._real_repository.get_missing_parent_inventories(
763
check_for_missing_texts=check_for_missing_texts)
765
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
767
return self._real_repository.get_rev_id_for_revno(
770
def get_rev_id_for_revno(self, revno, known_pair):
771
"""See Repository.get_rev_id_for_revno."""
772
path = self.bzrdir._path_for_remote_call(self._client)
774
if self._client._medium._is_remote_before((1, 17)):
775
return self._get_rev_id_for_revno_vfs(revno, known_pair)
776
response = self._call(
777
'Repository.get_rev_id_for_revno', path, revno, known_pair)
778
except errors.UnknownSmartMethod:
779
self._client._medium._remember_remote_is_before((1, 17))
780
return self._get_rev_id_for_revno_vfs(revno, known_pair)
781
if response[0] == 'ok':
782
return True, response[1]
783
elif response[0] == 'history-incomplete':
784
known_pair = response[1:3]
785
for fallback in self._fallback_repositories:
786
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
791
# Not found in any fallbacks
792
return False, known_pair
794
raise errors.UnexpectedSmartServerResponse(response)
796
274
def _ensure_real(self):
797
275
"""Ensure that there is a _real_repository set.
799
277
Used before calls to self._real_repository.
801
Note that _ensure_real causes many roundtrips to the server which are
802
not desirable, and prevents the use of smart one-roundtrip RPC's to
803
perform complex operations (such as accessing parent data, streaming
804
revisions etc). Adding calls to _ensure_real should only be done when
805
bringing up new functionality, adding fallbacks for smart methods that
806
require a fallback path, and never to replace an existing smart method
807
invocation. If in doubt chat to the bzr network team.
809
if self._real_repository is None:
810
if 'hpssvfs' in debug.debug_flags:
812
warning('VFS Repository access triggered\n%s',
813
''.join(traceback.format_stack()))
814
self._unstacked_provider.missing_keys.clear()
279
if not self._real_repository:
815
280
self.bzrdir._ensure_real()
816
self._set_real_repository(
817
self.bzrdir._real_bzrdir.open_repository())
819
def _translate_error(self, err, **context):
820
self.bzrdir._translate_error(err, repository=self, **context)
822
def find_text_key_references(self):
823
"""Find the text key references within the repository.
825
:return: a dictionary mapping (file_id, revision_id) tuples to altered file-ids to an iterable of
826
revision_ids. Each altered file-ids has the exact revision_ids that
827
altered it listed explicitly.
828
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
829
to whether they were referred to by the inventory of the
830
revision_id that they contain. The inventory texts from all present
831
revision ids are assessed to generate this report.
834
return self._real_repository.find_text_key_references()
836
def _generate_text_key_index(self):
837
"""Generate a new text key index for the repository.
839
This is an expensive function that will take considerable time to run.
841
:return: A dict mapping (file_id, revision_id) tuples to a list of
842
parents, also (file_id, revision_id) tuples.
845
return self._real_repository._generate_text_key_index()
847
def _get_revision_graph(self, revision_id):
848
"""Private method for using with old (< 1.2) servers to fallback."""
281
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
282
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
284
def get_revision_graph(self, revision_id=None):
285
"""See Repository.get_revision_graph()."""
849
286
if revision_id is None:
851
elif revision.is_null(revision_id):
288
elif revision_id == NULL_REVISION:
854
291
path = self.bzrdir._path_for_remote_call(self._client)
855
response = self._call_expecting_body(
292
assert type(revision_id) is str
293
response = self._client.call_expecting_body(
856
294
'Repository.get_revision_graph', path, revision_id)
857
response_tuple, response_handler = response
858
if response_tuple[0] != 'ok':
859
raise errors.UnexpectedSmartServerResponse(response_tuple)
860
coded = response_handler.read_body_bytes()
862
# no revisions in this repository!
864
lines = coded.split('\n')
867
d = tuple(line.split())
868
revision_graph[d[0]] = d[1:]
870
return revision_graph
873
"""See Repository._get_sink()."""
874
return RemoteStreamSink(self)
876
def _get_source(self, to_format):
877
"""Return a source for streaming from this repository."""
878
return RemoteStreamSource(self, to_format)
295
if response[0][0] not in ['ok', 'nosuchrevision']:
296
raise errors.UnexpectedSmartServerResponse(response[0])
297
if response[0][0] == 'ok':
298
coded = response[1].read_body_bytes()
300
# no revisions in this repository!
302
lines = coded.split('\n')
305
d = tuple(line.split())
306
revision_graph[d[0]] = d[1:]
308
return revision_graph
310
response_body = response[1].read_body_bytes()
311
assert response_body == ''
312
raise NoSuchRevision(self, revision_id)
881
314
def has_revision(self, revision_id):
882
"""True if this repository has a copy of the revision."""
883
# Copy of bzrlib.repository.Repository.has_revision
884
return revision_id in self.has_revisions((revision_id,))
887
def has_revisions(self, revision_ids):
888
"""Probe to find out the presence of multiple revisions.
890
:param revision_ids: An iterable of revision_ids.
891
:return: A set of the revision_ids that were present.
893
# Copy of bzrlib.repository.Repository.has_revisions
894
parent_map = self.get_parent_map(revision_ids)
895
result = set(parent_map)
896
if _mod_revision.NULL_REVISION in revision_ids:
897
result.add(_mod_revision.NULL_REVISION)
900
def _has_same_fallbacks(self, other_repo):
901
"""Returns true if the repositories have the same fallbacks."""
902
# XXX: copied from Repository; it should be unified into a base class
903
# <https://bugs.launchpad.net/bzr/+bug/401622>
904
my_fb = self._fallback_repositories
905
other_fb = other_repo._fallback_repositories
906
if len(my_fb) != len(other_fb):
908
for f, g in zip(my_fb, other_fb):
909
if not f.has_same_location(g):
315
"""See Repository.has_revision()."""
316
if revision_id is None:
317
# The null revision is always present.
319
path = self.bzrdir._path_for_remote_call(self._client)
320
response = self._client.call('Repository.has_revision', path, revision_id)
321
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
322
return response[0] == 'yes'
913
324
def has_same_location(self, other):
914
# TODO: Move to RepositoryBase and unify with the regular Repository
915
# one; unfortunately the tests rely on slightly different behaviour at
916
# present -- mbp 20090710
917
return (self.__class__ is other.__class__ and
325
return (self.__class__ == other.__class__ and
918
326
self.bzrdir.transport.base == other.bzrdir.transport.base)
920
328
def get_graph(self, other_repository=None):
921
329
"""Return the graph for this repository format"""
922
parents_provider = self._make_parents_provider(other_repository)
923
return graph.Graph(parents_provider)
926
def get_known_graph_ancestry(self, revision_ids):
927
"""Return the known graph for a set of revision ids and their ancestors.
929
st = static_tuple.StaticTuple
930
revision_keys = [st(r_id).intern() for r_id in revision_ids]
931
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
932
return graph.GraphThunkIdsToKeys(known_graph)
330
return self._real_repository.get_graph(other_repository)
934
332
def gather_stats(self, revid=None, committers=None):
935
333
"""See Repository.gather_stats()."""
936
334
path = self.bzrdir._path_for_remote_call(self._client)
937
# revid can be None to indicate no revisions, not just NULL_REVISION
938
if revid is None or revision.is_null(revid):
335
if revid in (None, NULL_REVISION):
941
338
fmt_revid = revid
1311
576
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1313
578
def make_working_trees(self):
1314
"""See Repository.make_working_trees"""
579
"""RemoteRepositories never create working trees by default."""
582
def fetch(self, source, revision_id=None, pb=None):
1315
583
self._ensure_real()
1316
return self._real_repository.make_working_trees()
1318
def refresh_data(self):
1319
"""Re-read any data needed to synchronise with disk.
1321
This method is intended to be called after another repository instance
1322
(such as one used by a smart server) has inserted data into the
1323
repository. On all repositories this will work outside of write groups.
1324
Some repository formats (pack and newer for bzrlib native formats)
1325
support refresh_data inside write groups. If called inside a write
1326
group on a repository that does not support refreshing in a write group
1327
IsInWriteGroupError will be raised.
1329
if self._real_repository is not None:
1330
self._real_repository.refresh_data()
1332
def revision_ids_to_search_result(self, result_set):
1333
"""Convert a set of revision ids to a graph SearchResult."""
1334
result_parents = set()
1335
for parents in self.get_graph().get_parent_map(
1336
result_set).itervalues():
1337
result_parents.update(parents)
1338
included_keys = result_set.intersection(result_parents)
1339
start_keys = result_set.difference(included_keys)
1340
exclude_keys = result_parents.difference(result_set)
1341
result = graph.SearchResult(start_keys, exclude_keys,
1342
len(result_set), result_set)
1346
def search_missing_revision_ids(self, other, revision_id=None, find_ghosts=True):
1347
"""Return the revision ids that other has that this does not.
1349
These are returned in topological order.
1351
revision_id: only return revision ids included by revision_id.
1353
return repository.InterRepository.get(
1354
other, self).search_missing_revision_ids(revision_id, find_ghosts)
1356
def fetch(self, source, revision_id=None, pb=None, find_ghosts=False,
1358
# No base implementation to use as RemoteRepository is not a subclass
1359
# of Repository; so this is a copy of Repository.fetch().
1360
if fetch_spec is not None and revision_id is not None:
1361
raise AssertionError(
1362
"fetch_spec and revision_id are mutually exclusive.")
1363
if self.is_in_write_group():
1364
raise errors.InternalBzrError(
1365
"May not fetch while in a write group.")
1366
# fast path same-url fetch operations
1367
if (self.has_same_location(source)
1368
and fetch_spec is None
1369
and self._has_same_fallbacks(source)):
1370
# check that last_revision is in 'from' and then return a
1372
if (revision_id is not None and
1373
not revision.is_null(revision_id)):
1374
self.get_revision(revision_id)
1376
# if there is no specific appropriate InterRepository, this will get
1377
# the InterRepository base class, which raises an
1378
# IncompatibleRepositories when asked to fetch.
1379
inter = repository.InterRepository.get(source, self)
1380
return inter.fetch(revision_id=revision_id, pb=pb,
1381
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
584
return self._real_repository.fetch(
585
source, revision_id=revision_id, pb=pb)
1383
587
def create_bundle(self, target, base, fileobj, format=None):
1384
588
self._ensure_real()
1385
589
self._real_repository.create_bundle(target, base, fileobj, format)
592
def control_weaves(self):
594
return self._real_repository.control_weaves
1387
596
@needs_read_lock
1388
597
def get_ancestry(self, revision_id, topo_sorted=True):
1389
598
self._ensure_real()
1390
599
return self._real_repository.get_ancestry(revision_id, topo_sorted)
602
def get_inventory_weave(self):
604
return self._real_repository.get_inventory_weave()
1392
606
def fileids_altered_by_revision_ids(self, revision_ids):
1393
607
self._ensure_real()
1394
608
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1396
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1398
return self._real_repository._get_versioned_file_checker(
1399
revisions, revision_versions_cache)
1401
def iter_files_bytes(self, desired_files):
1402
"""See Repository.iter_file_bytes.
1405
return self._real_repository.iter_files_bytes(desired_files)
1407
def get_parent_map(self, revision_ids):
1408
"""See bzrlib.Graph.get_parent_map()."""
1409
return self._make_parents_provider().get_parent_map(revision_ids)
1411
def _get_parent_map_rpc(self, keys):
1412
"""Helper for get_parent_map that performs the RPC."""
1413
medium = self._client._medium
1414
if medium._is_remote_before((1, 2)):
1415
# We already found out that the server can't understand
1416
# Repository.get_parent_map requests, so just fetch the whole
1419
# Note that this reads the whole graph, when only some keys are
1420
# wanted. On this old server there's no way (?) to get them all
1421
# in one go, and the user probably will have seen a warning about
1422
# the server being old anyhow.
1423
rg = self._get_revision_graph(None)
1424
# There is an API discrepancy between get_parent_map and
1425
# get_revision_graph. Specifically, a "key:()" pair in
1426
# get_revision_graph just means a node has no parents. For
1427
# "get_parent_map" it means the node is a ghost. So fix up the
1428
# graph to correct this.
1429
# https://bugs.launchpad.net/bzr/+bug/214894
1430
# There is one other "bug" which is that ghosts in
1431
# get_revision_graph() are not returned at all. But we won't worry
1432
# about that for now.
1433
for node_id, parent_ids in rg.iteritems():
1434
if parent_ids == ():
1435
rg[node_id] = (NULL_REVISION,)
1436
rg[NULL_REVISION] = ()
1441
raise ValueError('get_parent_map(None) is not valid')
1442
if NULL_REVISION in keys:
1443
keys.discard(NULL_REVISION)
1444
found_parents = {NULL_REVISION:()}
1446
return found_parents
1449
# TODO(Needs analysis): We could assume that the keys being requested
1450
# from get_parent_map are in a breadth first search, so typically they
1451
# will all be depth N from some common parent, and we don't have to
1452
# have the server iterate from the root parent, but rather from the
1453
# keys we're searching; and just tell the server the keyspace we
1454
# already have; but this may be more traffic again.
1456
# Transform self._parents_map into a search request recipe.
1457
# TODO: Manage this incrementally to avoid covering the same path
1458
# repeatedly. (The server will have to on each request, but the less
1459
# work done the better).
1461
# Negative caching notes:
1462
# new server sends missing when a request including the revid
1463
# 'include-missing:' is present in the request.
1464
# missing keys are serialised as missing:X, and we then call
1465
# provider.note_missing(X) for-all X
1466
parents_map = self._unstacked_provider.get_cached_map()
1467
if parents_map is None:
1468
# Repository is not locked, so there's no cache.
1470
# start_set is all the keys in the cache
1471
start_set = set(parents_map)
1472
# result set is all the references to keys in the cache
1473
result_parents = set()
1474
for parents in parents_map.itervalues():
1475
result_parents.update(parents)
1476
stop_keys = result_parents.difference(start_set)
1477
# We don't need to send ghosts back to the server as a position to
1479
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1480
key_count = len(parents_map)
1481
if (NULL_REVISION in result_parents
1482
and NULL_REVISION in self._unstacked_provider.missing_keys):
1483
# If we pruned NULL_REVISION from the stop_keys because it's also
1484
# in our cache of "missing" keys we need to increment our key count
1485
# by 1, because the reconsitituted SearchResult on the server will
1486
# still consider NULL_REVISION to be an included key.
1488
included_keys = start_set.intersection(result_parents)
1489
start_set.difference_update(included_keys)
1490
recipe = ('manual', start_set, stop_keys, key_count)
1491
body = self._serialise_search_recipe(recipe)
1492
path = self.bzrdir._path_for_remote_call(self._client)
1494
if type(key) is not str:
1496
"key %r not a plain string" % (key,))
1497
verb = 'Repository.get_parent_map'
1498
args = (path, 'include-missing:') + tuple(keys)
1500
response = self._call_with_body_bytes_expecting_body(
1502
except errors.UnknownSmartMethod:
1503
# Server does not support this method, so get the whole graph.
1504
# Worse, we have to force a disconnection, because the server now
1505
# doesn't realise it has a body on the wire to consume, so the
1506
# only way to recover is to abandon the connection.
1508
'Server is too old for fast get_parent_map, reconnecting. '
1509
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1511
# To avoid having to disconnect repeatedly, we keep track of the
1512
# fact the server doesn't understand remote methods added in 1.2.
1513
medium._remember_remote_is_before((1, 2))
1514
# Recurse just once and we should use the fallback code.
1515
return self._get_parent_map_rpc(keys)
1516
response_tuple, response_handler = response
1517
if response_tuple[0] not in ['ok']:
1518
response_handler.cancel_read_body()
1519
raise errors.UnexpectedSmartServerResponse(response_tuple)
1520
if response_tuple[0] == 'ok':
1521
coded = bz2.decompress(response_handler.read_body_bytes())
1523
# no revisions found
1525
lines = coded.split('\n')
1528
d = tuple(line.split())
1530
revision_graph[d[0]] = d[1:]
1533
if d[0].startswith('missing:'):
1535
self._unstacked_provider.note_missing_key(revid)
1537
# no parents - so give the Graph result
1539
revision_graph[d[0]] = (NULL_REVISION,)
1540
return revision_graph
1542
610
@needs_read_lock
1543
611
def get_signature_text(self, revision_id):
1544
612
self._ensure_real()
1545
613
return self._real_repository.get_signature_text(revision_id)
1547
615
@needs_read_lock
1548
def _get_inventory_xml(self, revision_id):
1550
return self._real_repository._get_inventory_xml(revision_id)
616
def get_revision_graph_with_ghosts(self, revision_ids=None):
618
return self._real_repository.get_revision_graph_with_ghosts(
619
revision_ids=revision_ids)
622
def get_inventory_xml(self, revision_id):
624
return self._real_repository.get_inventory_xml(revision_id)
626
def deserialise_inventory(self, revision_id, xml):
628
return self._real_repository.deserialise_inventory(revision_id, xml)
1552
630
def reconcile(self, other=None, thorough=False):
1553
631
self._ensure_real()
1554
632
return self._real_repository.reconcile(other=other, thorough=thorough)
1556
634
def all_revision_ids(self):
1557
635
self._ensure_real()
1558
636
return self._real_repository.all_revision_ids()
1561
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1563
return self._real_repository.get_deltas_for_revisions(revisions,
1564
specific_fileids=specific_fileids)
1567
def get_revision_delta(self, revision_id, specific_fileids=None):
1569
return self._real_repository.get_revision_delta(revision_id,
1570
specific_fileids=specific_fileids)
639
def get_deltas_for_revisions(self, revisions):
641
return self._real_repository.get_deltas_for_revisions(revisions)
644
def get_revision_delta(self, revision_id):
646
return self._real_repository.get_revision_delta(revision_id)
1572
648
@needs_read_lock
1573
649
def revision_trees(self, revision_ids):
1706
728
def _serializer(self):
1707
return self._format._serializer
730
return self._real_repository._serializer
1709
732
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1710
733
self._ensure_real()
1711
734
return self._real_repository.store_revision_signature(
1712
735
gpg_strategy, plaintext, revision_id)
1714
def add_signature_text(self, revision_id, signature):
1716
return self._real_repository.add_signature_text(revision_id, signature)
1718
737
def has_signature_for_revision_id(self, revision_id):
1719
738
self._ensure_real()
1720
739
return self._real_repository.has_signature_for_revision_id(revision_id)
1722
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
1724
return self._real_repository.item_keys_introduced_by(revision_ids,
1725
_files_pb=_files_pb)
1727
def revision_graph_can_have_wrong_parents(self):
1728
# The answer depends on the remote repo format.
1730
return self._real_repository.revision_graph_can_have_wrong_parents()
1732
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
1734
return self._real_repository._find_inconsistent_revision_parents(
1737
def _check_for_inconsistent_revision_parents(self):
1739
return self._real_repository._check_for_inconsistent_revision_parents()
1741
def _make_parents_provider(self, other=None):
1742
providers = [self._unstacked_provider]
1743
if other is not None:
1744
providers.insert(0, other)
1745
providers.extend(r._make_parents_provider() for r in
1746
self._fallback_repositories)
1747
return graph.StackedParentsProvider(providers)
1749
def _serialise_search_recipe(self, recipe):
1750
"""Serialise a graph search recipe.
1752
:param recipe: A search recipe (start, stop, count).
1753
:return: Serialised bytes.
1755
start_keys = ' '.join(recipe[1])
1756
stop_keys = ' '.join(recipe[2])
1757
count = str(recipe[3])
1758
return '\n'.join((start_keys, stop_keys, count))
1760
def _serialise_search_result(self, search_result):
1761
if isinstance(search_result, graph.PendingAncestryResult):
1762
parts = ['ancestry-of']
1763
parts.extend(search_result.heads)
1765
recipe = search_result.get_recipe()
1766
parts = [recipe[0], self._serialise_search_recipe(recipe)]
1767
return '\n'.join(parts)
1770
path = self.bzrdir._path_for_remote_call(self._client)
1772
response = self._call('PackRepository.autopack', path)
1773
except errors.UnknownSmartMethod:
1775
self._real_repository._pack_collection.autopack()
1778
if response[0] != 'ok':
1779
raise errors.UnexpectedSmartServerResponse(response)
1782
class RemoteStreamSink(repository.StreamSink):
1784
def _insert_real(self, stream, src_format, resume_tokens):
1785
self.target_repo._ensure_real()
1786
sink = self.target_repo._real_repository._get_sink()
1787
result = sink.insert_stream(stream, src_format, resume_tokens)
1789
self.target_repo.autopack()
1792
def insert_stream(self, stream, src_format, resume_tokens):
1793
target = self.target_repo
1794
target._unstacked_provider.missing_keys.clear()
1795
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
1796
if target._lock_token:
1797
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
1798
lock_args = (target._lock_token or '',)
1800
candidate_calls.append(('Repository.insert_stream', (1, 13)))
1802
client = target._client
1803
medium = client._medium
1804
path = target.bzrdir._path_for_remote_call(client)
1805
# Probe for the verb to use with an empty stream before sending the
1806
# real stream to it. We do this both to avoid the risk of sending a
1807
# large request that is then rejected, and because we don't want to
1808
# implement a way to buffer, rewind, or restart the stream.
1810
for verb, required_version in candidate_calls:
1811
if medium._is_remote_before(required_version):
1814
# We've already done the probing (and set _is_remote_before) on
1815
# a previous insert.
1818
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
1820
response = client.call_with_body_stream(
1821
(verb, path, '') + lock_args, byte_stream)
1822
except errors.UnknownSmartMethod:
1823
medium._remember_remote_is_before(required_version)
1829
return self._insert_real(stream, src_format, resume_tokens)
1830
self._last_inv_record = None
1831
self._last_substream = None
1832
if required_version < (1, 19):
1833
# Remote side doesn't support inventory deltas. Wrap the stream to
1834
# make sure we don't send any. If the stream contains inventory
1835
# deltas we'll interrupt the smart insert_stream request and
1837
stream = self._stop_stream_if_inventory_delta(stream)
1838
byte_stream = smart_repo._stream_to_byte_stream(
1840
resume_tokens = ' '.join(resume_tokens)
1841
response = client.call_with_body_stream(
1842
(verb, path, resume_tokens) + lock_args, byte_stream)
1843
if response[0][0] not in ('ok', 'missing-basis'):
1844
raise errors.UnexpectedSmartServerResponse(response)
1845
if self._last_substream is not None:
1846
# The stream included an inventory-delta record, but the remote
1847
# side isn't new enough to support them. So we need to send the
1848
# rest of the stream via VFS.
1849
self.target_repo.refresh_data()
1850
return self._resume_stream_with_vfs(response, src_format)
1851
if response[0][0] == 'missing-basis':
1852
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1853
resume_tokens = tokens
1854
return resume_tokens, set(missing_keys)
1856
self.target_repo.refresh_data()
1859
def _resume_stream_with_vfs(self, response, src_format):
1860
"""Resume sending a stream via VFS, first resending the record and
1861
substream that couldn't be sent via an insert_stream verb.
1863
if response[0][0] == 'missing-basis':
1864
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
1865
# Ignore missing_keys, we haven't finished inserting yet
1868
def resume_substream():
1869
# Yield the substream that was interrupted.
1870
for record in self._last_substream:
1872
self._last_substream = None
1873
def resume_stream():
1874
# Finish sending the interrupted substream
1875
yield ('inventory-deltas', resume_substream())
1876
# Then simply continue sending the rest of the stream.
1877
for substream_kind, substream in self._last_stream:
1878
yield substream_kind, substream
1879
return self._insert_real(resume_stream(), src_format, tokens)
1881
def _stop_stream_if_inventory_delta(self, stream):
1882
"""Normally this just lets the original stream pass-through unchanged.
1884
However if any 'inventory-deltas' substream occurs it will stop
1885
streaming, and store the interrupted substream and stream in
1886
self._last_substream and self._last_stream so that the stream can be
1887
resumed by _resume_stream_with_vfs.
1890
stream_iter = iter(stream)
1891
for substream_kind, substream in stream_iter:
1892
if substream_kind == 'inventory-deltas':
1893
self._last_substream = substream
1894
self._last_stream = stream_iter
1897
yield substream_kind, substream
1900
class RemoteStreamSource(repository.StreamSource):
1901
"""Stream data from a remote server."""
1903
def get_stream(self, search):
1904
if (self.from_repository._fallback_repositories and
1905
self.to_format._fetch_order == 'topological'):
1906
return self._real_stream(self.from_repository, search)
1909
repos = [self.from_repository]
1915
repos.extend(repo._fallback_repositories)
1916
sources.append(repo)
1917
return self.missing_parents_chain(search, sources)
1919
def get_stream_for_missing_keys(self, missing_keys):
1920
self.from_repository._ensure_real()
1921
real_repo = self.from_repository._real_repository
1922
real_source = real_repo._get_source(self.to_format)
1923
return real_source.get_stream_for_missing_keys(missing_keys)
1925
def _real_stream(self, repo, search):
1926
"""Get a stream for search from repo.
1928
This never called RemoteStreamSource.get_stream, and is a heler
1929
for RemoteStreamSource._get_stream to allow getting a stream
1930
reliably whether fallback back because of old servers or trying
1931
to stream from a non-RemoteRepository (which the stacked support
1934
source = repo._get_source(self.to_format)
1935
if isinstance(source, RemoteStreamSource):
1937
source = repo._real_repository._get_source(self.to_format)
1938
return source.get_stream(search)
1940
def _get_stream(self, repo, search):
1941
"""Core worker to get a stream from repo for search.
1943
This is used by both get_stream and the stacking support logic. It
1944
deliberately gets a stream for repo which does not need to be
1945
self.from_repository. In the event that repo is not Remote, or
1946
cannot do a smart stream, a fallback is made to the generic
1947
repository._get_stream() interface, via self._real_stream.
1949
In the event of stacking, streams from _get_stream will not
1950
contain all the data for search - this is normal (see get_stream).
1952
:param repo: A repository.
1953
:param search: A search.
1955
# Fallbacks may be non-smart
1956
if not isinstance(repo, RemoteRepository):
1957
return self._real_stream(repo, search)
1958
client = repo._client
1959
medium = client._medium
1960
path = repo.bzrdir._path_for_remote_call(client)
1961
search_bytes = repo._serialise_search_result(search)
1962
args = (path, self.to_format.network_name())
1964
('Repository.get_stream_1.19', (1, 19)),
1965
('Repository.get_stream', (1, 13))]
1967
for verb, version in candidate_verbs:
1968
if medium._is_remote_before(version):
1971
response = repo._call_with_body_bytes_expecting_body(
1972
verb, args, search_bytes)
1973
except errors.UnknownSmartMethod:
1974
medium._remember_remote_is_before(version)
1976
response_tuple, response_handler = response
1980
return self._real_stream(repo, search)
1981
if response_tuple[0] != 'ok':
1982
raise errors.UnexpectedSmartServerResponse(response_tuple)
1983
byte_stream = response_handler.read_streamed_body()
1984
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
1985
self._record_counter)
1986
if src_format.network_name() != repo._format.network_name():
1987
raise AssertionError(
1988
"Mismatched RemoteRepository and stream src %r, %r" % (
1989
src_format.network_name(), repo._format.network_name()))
1992
def missing_parents_chain(self, search, sources):
1993
"""Chain multiple streams together to handle stacking.
1995
:param search: The overall search to satisfy with streams.
1996
:param sources: A list of Repository objects to query.
1998
self.from_serialiser = self.from_repository._format._serializer
1999
self.seen_revs = set()
2000
self.referenced_revs = set()
2001
# If there are heads in the search, or the key count is > 0, we are not
2003
while not search.is_empty() and len(sources) > 1:
2004
source = sources.pop(0)
2005
stream = self._get_stream(source, search)
2006
for kind, substream in stream:
2007
if kind != 'revisions':
2008
yield kind, substream
2010
yield kind, self.missing_parents_rev_handler(substream)
2011
search = search.refine(self.seen_revs, self.referenced_revs)
2012
self.seen_revs = set()
2013
self.referenced_revs = set()
2014
if not search.is_empty():
2015
for kind, stream in self._get_stream(sources[0], search):
2018
def missing_parents_rev_handler(self, substream):
2019
for content in substream:
2020
revision_bytes = content.get_bytes_as('fulltext')
2021
revision = self.from_serialiser.read_revision_from_string(
2023
self.seen_revs.add(content.key[-1])
2024
self.referenced_revs.update(revision.parent_ids)
2028
742
class RemoteBranchLockableFiles(LockableFiles):
2029
743
"""A 'LockableFiles' implementation that talks to a smart server.
2031
745
This is not a public interface class.
2044
758
self._dir_mode = None
2045
759
self._file_mode = None
762
"""'get' a remote path as per the LockableFiles interface.
764
:param path: the file to 'get'. If this is 'branch.conf', we do not
765
just retrieve a file, instead we ask the smart server to generate
766
a configuration for us - which is retrieved as an INI file.
768
if path == 'branch.conf':
769
path = self.bzrdir._path_for_remote_call(self._client)
770
response = self._client.call_expecting_body(
771
'Branch.get_config_file', path)
772
assert response[0][0] == 'ok', \
773
'unexpected response code %s' % (response[0],)
774
return StringIO(response[1].read_body_bytes())
777
return LockableFiles.get(self, path)
2048
780
class RemoteBranchFormat(branch.BranchFormat):
2050
def __init__(self, network_name=None):
2051
super(RemoteBranchFormat, self).__init__()
2052
self._matchingbzrdir = RemoteBzrDirFormat()
2053
self._matchingbzrdir.set_branch_format(self)
2054
self._custom_format = None
2055
self._network_name = network_name
2057
782
def __eq__(self, other):
2058
return (isinstance(other, RemoteBranchFormat) and
783
return (isinstance(other, RemoteBranchFormat) and
2059
784
self.__dict__ == other.__dict__)
2061
def _ensure_real(self):
2062
if self._custom_format is None:
2063
self._custom_format = branch.network_format_registry.get(
2066
786
def get_format_description(self):
2068
return 'Remote: ' + self._custom_format.get_format_description()
2070
def network_name(self):
2071
return self._network_name
2073
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2074
return a_bzrdir.open_branch(name=name,
2075
ignore_fallbacks=ignore_fallbacks)
2077
def _vfs_initialize(self, a_bzrdir, name):
2078
# Initialisation when using a local bzrdir object, or a non-vfs init
2079
# method is not available on the server.
2080
# self._custom_format is always set - the start of initialize ensures
2082
if isinstance(a_bzrdir, RemoteBzrDir):
2083
a_bzrdir._ensure_real()
2084
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2087
# We assume the bzrdir is parameterised; it may not be.
2088
result = self._custom_format.initialize(a_bzrdir, name)
2089
if (isinstance(a_bzrdir, RemoteBzrDir) and
2090
not isinstance(result, RemoteBranch)):
2091
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2095
def initialize(self, a_bzrdir, name=None):
2096
# 1) get the network name to use.
2097
if self._custom_format:
2098
network_name = self._custom_format.network_name()
2100
# Select the current bzrlib default and ask for that.
2101
reference_bzrdir_format = bzrdir.format_registry.get('default')()
2102
reference_format = reference_bzrdir_format.get_branch_format()
2103
self._custom_format = reference_format
2104
network_name = reference_format.network_name()
2105
# Being asked to create on a non RemoteBzrDir:
2106
if not isinstance(a_bzrdir, RemoteBzrDir):
2107
return self._vfs_initialize(a_bzrdir, name=name)
2108
medium = a_bzrdir._client._medium
2109
if medium._is_remote_before((1, 13)):
2110
return self._vfs_initialize(a_bzrdir, name=name)
2111
# Creating on a remote bzr dir.
2112
# 2) try direct creation via RPC
2113
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2114
if name is not None:
2115
# XXX JRV20100304: Support creating colocated branches
2116
raise errors.NoColocatedBranchSupport(self)
2117
verb = 'BzrDir.create_branch'
2119
response = a_bzrdir._call(verb, path, network_name)
2120
except errors.UnknownSmartMethod:
2121
# Fallback - use vfs methods
2122
medium._remember_remote_is_before((1, 13))
2123
return self._vfs_initialize(a_bzrdir, name=name)
2124
if response[0] != 'ok':
2125
raise errors.UnexpectedSmartServerResponse(response)
2126
# Turn the response into a RemoteRepository object.
2127
format = RemoteBranchFormat(network_name=response[1])
2128
repo_format = response_tuple_to_repo_format(response[3:])
2129
if response[2] == '':
2130
repo_bzrdir = a_bzrdir
2132
repo_bzrdir = RemoteBzrDir(
2133
a_bzrdir.root_transport.clone(response[2]), a_bzrdir._format,
2135
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2136
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2137
format=format, setup_stacking=False, name=name)
2138
# XXX: We know this is a new branch, so it must have revno 0, revid
2139
# NULL_REVISION. Creating the branch locked would make this be unable
2140
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2141
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2142
return remote_branch
2144
def make_tags(self, branch):
2146
return self._custom_format.make_tags(branch)
2148
def supports_tags(self):
2149
# Remote branches might support tags, but we won't know until we
2150
# access the real remote branch.
2152
return self._custom_format.supports_tags()
2154
def supports_stacking(self):
2156
return self._custom_format.supports_stacking()
2158
def supports_set_append_revisions_only(self):
2160
return self._custom_format.supports_set_append_revisions_only()
2163
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
787
return 'Remote BZR Branch'
789
def get_format_string(self):
790
return 'Remote BZR Branch'
792
def open(self, a_bzrdir):
793
assert isinstance(a_bzrdir, RemoteBzrDir)
794
return a_bzrdir.open_branch()
796
def initialize(self, a_bzrdir):
797
assert isinstance(a_bzrdir, RemoteBzrDir)
798
return a_bzrdir.create_branch()
801
class RemoteBranch(branch.Branch):
2164
802
"""Branch stored on a server accessed by HPSS RPC.
2166
804
At the moment most operations are mapped down to simple file operations.
2169
807
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2170
_client=None, format=None, setup_stacking=True, name=None):
2171
809
"""Create a RemoteBranch instance.
2173
811
:param real_branch: An optional local implementation of the branch
2174
812
format, usually accessing the data via the VFS.
2175
813
:param _client: Private parameter for testing.
2176
:param format: A RemoteBranchFormat object, None to create one
2177
automatically. If supplied it should have a network_name already
2179
:param setup_stacking: If True make an RPC call to determine the
2180
stacked (or not) status of the branch. If False assume the branch
2182
:param name: Colocated branch name
2184
815
# We intentionally don't call the parent class's __init__, because it
2185
816
# will try to assign to self.tags, which is a property in this subclass.
2186
817
# And the parent's __init__ doesn't do much anyway.
818
self._revision_history_cache = None
2187
819
self.bzrdir = remote_bzrdir
2188
820
if _client is not None:
2189
821
self._client = _client
2191
self._client = remote_bzrdir._client
823
self._client = client._SmartClient(self.bzrdir._shared_medium)
2192
824
self.repository = remote_repository
2193
825
if real_branch is not None:
2194
826
self._real_branch = real_branch
2332
888
self._ensure_real()
2333
889
return self._real_branch.get_physical_lock_status()
2335
def get_stacked_on_url(self):
2336
"""Get the URL this branch is stacked against.
2338
:raises NotStacked: If the branch is not stacked.
2339
:raises UnstackableBranchFormat: If the branch does not support
2341
:raises UnstackableRepositoryFormat: If the repository does not support
2345
# there may not be a repository yet, so we can't use
2346
# self._translate_error, so we can't use self._call either.
2347
response = self._client.call('Branch.get_stacked_on_url',
2348
self._remote_path())
2349
except errors.ErrorFromSmartServer, err:
2350
# there may not be a repository yet, so we can't call through
2351
# its _translate_error
2352
_translate_error(err, branch=self)
2353
except errors.UnknownSmartMethod, err:
2355
return self._real_branch.get_stacked_on_url()
2356
if response[0] != 'ok':
2357
raise errors.UnexpectedSmartServerResponse(response)
2360
def set_stacked_on_url(self, url):
2361
branch.Branch.set_stacked_on_url(self, url)
2363
self._is_stacked = False
2365
self._is_stacked = True
2367
def _vfs_get_tags_bytes(self):
2369
return self._real_branch._get_tags_bytes()
2371
def _get_tags_bytes(self):
2372
medium = self._client._medium
2373
if medium._is_remote_before((1, 13)):
2374
return self._vfs_get_tags_bytes()
2376
response = self._call('Branch.get_tags_bytes', self._remote_path())
2377
except errors.UnknownSmartMethod:
2378
medium._remember_remote_is_before((1, 13))
2379
return self._vfs_get_tags_bytes()
2382
def _vfs_set_tags_bytes(self, bytes):
2384
return self._real_branch._set_tags_bytes(bytes)
2386
def _set_tags_bytes(self, bytes):
2387
medium = self._client._medium
2388
if medium._is_remote_before((1, 18)):
2389
self._vfs_set_tags_bytes(bytes)
2393
self._remote_path(), self._lock_token, self._repo_lock_token)
2394
response = self._call_with_body_bytes(
2395
'Branch.set_tags_bytes', args, bytes)
2396
except errors.UnknownSmartMethod:
2397
medium._remember_remote_is_before((1, 18))
2398
self._vfs_set_tags_bytes(bytes)
2400
891
def lock_read(self):
2401
"""Lock the branch for read operations.
2403
:return: A bzrlib.lock.LogicalLockResult.
2405
self.repository.lock_read()
2406
892
if not self._lock_mode:
2407
self._note_lock('r')
2408
893
self._lock_mode = 'r'
2409
894
self._lock_count = 1
2410
895
if self._real_branch is not None:
2411
896
self._real_branch.lock_read()
2413
898
self._lock_count += 1
2414
return lock.LogicalLockResult(self.unlock)
2416
900
def _remote_lock_write(self, token):
2417
901
if token is None:
2418
902
branch_token = repo_token = ''
2420
904
branch_token = token
2421
repo_token = self.repository.lock_write().repository_token
905
repo_token = self.repository.lock_write()
2422
906
self.repository.unlock()
2423
err_context = {'token': token}
2425
response = self._call(
2426
'Branch.lock_write', self._remote_path(), branch_token,
2427
repo_token or '', **err_context)
2428
except errors.LockContention, e:
2429
# The LockContention from the server doesn't have any
2430
# information about the lock_url. We re-raise LockContention
2431
# with valid lock_url.
2432
raise errors.LockContention('(remote lock)',
2433
self.repository.base.split('.bzr/')[0])
2434
if response[0] != 'ok':
907
path = self.bzrdir._path_for_remote_call(self._client)
908
response = self._client.call('Branch.lock_write', path, branch_token,
910
if response[0] == 'ok':
911
ok, branch_token, repo_token = response
912
return branch_token, repo_token
913
elif response[0] == 'LockContention':
914
raise errors.LockContention('(remote lock)')
915
elif response[0] == 'TokenMismatch':
916
raise errors.TokenMismatch(token, '(remote token)')
917
elif response[0] == 'UnlockableTransport':
918
raise errors.UnlockableTransport(self.bzrdir.root_transport)
919
elif response[0] == 'ReadOnlyError':
920
raise errors.ReadOnlyError(self)
2435
922
raise errors.UnexpectedSmartServerResponse(response)
2436
ok, branch_token, repo_token = response
2437
return branch_token, repo_token
2439
924
def lock_write(self, token=None):
2440
925
if not self._lock_mode:
2441
self._note_lock('w')
2442
# Lock the branch and repo in one remote call.
2443
926
remote_tokens = self._remote_lock_write(token)
2444
927
self._lock_token, self._repo_lock_token = remote_tokens
2445
if not self._lock_token:
2446
raise SmartProtocolError('Remote server did not return a token!')
2447
# Tell the self.repository object that it is locked.
2448
self.repository.lock_write(
2449
self._repo_lock_token, _skip_rpc=True)
928
assert self._lock_token, 'Remote server did not return a token!'
929
# TODO: We really, really, really don't want to call _ensure_real
930
# here, but it's the easiest way to ensure coherency between the
931
# state of the RemoteBranch and RemoteRepository objects and the
932
# physical locks. If we don't materialise the real objects here,
933
# then getting everything in the right state later is complex, so
934
# for now we just do it the lazy way.
935
# -- Andrew Bennetts, 2007-02-22.
2451
937
if self._real_branch is not None:
2452
self._real_branch.lock_write(token=self._lock_token)
938
self._real_branch.repository.lock_write(
939
token=self._repo_lock_token)
941
self._real_branch.lock_write(token=self._lock_token)
943
self._real_branch.repository.unlock()
2453
944
if token is not None:
2454
945
self._leave_lock = True
947
# XXX: this case seems to be unreachable; token cannot be None.
2456
948
self._leave_lock = False
2457
949
self._lock_mode = 'w'
2458
950
self._lock_count = 1
2459
951
elif self._lock_mode == 'r':
2460
raise errors.ReadOnlyError(self)
952
raise errors.ReadOnlyTransaction
2462
954
if token is not None:
2463
# A token was given to lock_write, and we're relocking, so
2464
# check that the given token actually matches the one we
955
# A token was given to lock_write, and we're relocking, so check
956
# that the given token actually matches the one we already have.
2466
957
if token != self._lock_token:
2467
958
raise errors.TokenMismatch(token, self._lock_token)
2468
959
self._lock_count += 1
2469
# Re-lock the repository too.
2470
self.repository.lock_write(self._repo_lock_token)
2471
return BranchWriteLockResult(self.unlock, self._lock_token or None)
960
return self._lock_token
2473
962
def _unlock(self, branch_token, repo_token):
2474
err_context = {'token': str((branch_token, repo_token))}
2475
response = self._call(
2476
'Branch.unlock', self._remote_path(), branch_token,
2477
repo_token or '', **err_context)
963
path = self.bzrdir._path_for_remote_call(self._client)
964
response = self._client.call('Branch.unlock', path, branch_token,
2478
966
if response == ('ok',):
2480
raise errors.UnexpectedSmartServerResponse(response)
968
elif response[0] == 'TokenMismatch':
969
raise errors.TokenMismatch(
970
str((branch_token, repo_token)), '(remote tokens)')
972
raise errors.UnexpectedSmartServerResponse(response)
2482
@only_raises(errors.LockNotHeld, errors.LockBroken)
2483
974
def unlock(self):
2485
self._lock_count -= 1
2486
if not self._lock_count:
2487
self._clear_cached_state()
2488
mode = self._lock_mode
2489
self._lock_mode = None
2490
if self._real_branch is not None:
2491
if (not self._leave_lock and mode == 'w' and
2492
self._repo_lock_token):
2493
# If this RemoteBranch will remove the physical lock
2494
# for the repository, make sure the _real_branch
2495
# doesn't do it first. (Because the _real_branch's
2496
# repository is set to be the RemoteRepository.)
2497
self._real_branch.repository.leave_lock_in_place()
2498
self._real_branch.unlock()
2500
# Only write-locked branched need to make a remote method
2501
# call to perform the unlock.
2503
if not self._lock_token:
2504
raise AssertionError('Locked, but no token!')
2505
branch_token = self._lock_token
2506
repo_token = self._repo_lock_token
2507
self._lock_token = None
2508
self._repo_lock_token = None
975
self._lock_count -= 1
976
if not self._lock_count:
977
self._clear_cached_state()
978
mode = self._lock_mode
979
self._lock_mode = None
980
if self._real_branch is not None:
2509
981
if not self._leave_lock:
2510
self._unlock(branch_token, repo_token)
2512
self.repository.unlock()
982
# If this RemoteBranch will remove the physical lock for the
983
# repository, make sure the _real_branch doesn't do it
984
# first. (Because the _real_branch's repository is set to
985
# be the RemoteRepository.)
986
self._real_branch.repository.leave_lock_in_place()
987
self._real_branch.unlock()
989
# Only write-locked branched need to make a remote method call
990
# to perfom the unlock.
992
assert self._lock_token, 'Locked, but no token!'
993
branch_token = self._lock_token
994
repo_token = self._repo_lock_token
995
self._lock_token = None
996
self._repo_lock_token = None
997
if not self._leave_lock:
998
self._unlock(branch_token, repo_token)
2514
1000
def break_lock(self):
2515
1001
self._ensure_real()
2516
1002
return self._real_branch.break_lock()
2518
1004
def leave_lock_in_place(self):
2519
if not self._lock_token:
2520
raise NotImplementedError(self.leave_lock_in_place)
2521
1005
self._leave_lock = True
2523
1007
def dont_leave_lock_in_place(self):
2524
if not self._lock_token:
2525
raise NotImplementedError(self.dont_leave_lock_in_place)
2526
1008
self._leave_lock = False
2529
def get_rev_id(self, revno, history=None):
2531
return _mod_revision.NULL_REVISION
2532
last_revision_info = self.last_revision_info()
2533
ok, result = self.repository.get_rev_id_for_revno(
2534
revno, last_revision_info)
2537
missing_parent = result[1]
2538
# Either the revision named by the server is missing, or its parent
2539
# is. Call get_parent_map to determine which, so that we report a
2541
parent_map = self.repository.get_parent_map([missing_parent])
2542
if missing_parent in parent_map:
2543
missing_parent = parent_map[missing_parent]
2544
raise errors.RevisionNotPresent(missing_parent, self.repository)
2546
def _last_revision_info(self):
2547
response = self._call('Branch.last_revision_info', self._remote_path())
2548
if response[0] != 'ok':
2549
raise SmartProtocolError('unexpected response code %s' % (response,))
1010
def last_revision_info(self):
1011
"""See Branch.last_revision_info()."""
1012
path = self.bzrdir._path_for_remote_call(self._client)
1013
response = self._client.call('Branch.last_revision_info', path)
1014
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
2550
1015
revno = int(response[1])
2551
1016
last_revision = response[2]
2552
1017
return (revno, last_revision)
2554
1019
def _gen_revision_history(self):
2555
1020
"""See Branch._gen_revision_history()."""
2556
if self._is_stacked:
2558
return self._real_branch._gen_revision_history()
2559
response_tuple, response_handler = self._call_expecting_body(
2560
'Branch.revision_history', self._remote_path())
2561
if response_tuple[0] != 'ok':
2562
raise errors.UnexpectedSmartServerResponse(response_tuple)
2563
result = response_handler.read_body_bytes().split('\x00')
1021
path = self.bzrdir._path_for_remote_call(self._client)
1022
response = self._client.call_expecting_body(
1023
'Branch.revision_history', path)
1024
assert response[0][0] == 'ok', ('unexpected response code %s'
1026
result = response[1].read_body_bytes().split('\x00')
2564
1027
if result == ['']:
2568
def _remote_path(self):
2569
return self.bzrdir._path_for_remote_call(self._client)
2571
def _set_last_revision_descendant(self, revision_id, other_branch,
2572
allow_diverged=False, allow_overwrite_descendant=False):
2573
# This performs additional work to meet the hook contract; while its
2574
# undesirable, we have to synthesise the revno to call the hook, and
2575
# not calling the hook is worse as it means changes can't be prevented.
2576
# Having calculated this though, we can't just call into
2577
# set_last_revision_info as a simple call, because there is a set_rh
2578
# hook that some folk may still be using.
2579
old_revno, old_revid = self.last_revision_info()
2580
history = self._lefthand_history(revision_id)
2581
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2582
err_context = {'other_branch': other_branch}
2583
response = self._call('Branch.set_last_revision_ex',
2584
self._remote_path(), self._lock_token, self._repo_lock_token,
2585
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2587
self._clear_cached_state()
2588
if len(response) != 3 and response[0] != 'ok':
2589
raise errors.UnexpectedSmartServerResponse(response)
2590
new_revno, new_revision_id = response[1:]
2591
self._last_revision_info_cache = new_revno, new_revision_id
2592
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2593
if self._real_branch is not None:
2594
cache = new_revno, new_revision_id
2595
self._real_branch._last_revision_info_cache = cache
2597
def _set_last_revision(self, revision_id):
2598
old_revno, old_revid = self.last_revision_info()
2599
# This performs additional work to meet the hook contract; while its
2600
# undesirable, we have to synthesise the revno to call the hook, and
2601
# not calling the hook is worse as it means changes can't be prevented.
2602
# Having calculated this though, we can't just call into
2603
# set_last_revision_info as a simple call, because there is a set_rh
2604
# hook that some folk may still be using.
2605
history = self._lefthand_history(revision_id)
2606
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2607
self._clear_cached_state()
2608
response = self._call('Branch.set_last_revision',
2609
self._remote_path(), self._lock_token, self._repo_lock_token,
2611
if response != ('ok',):
2612
raise errors.UnexpectedSmartServerResponse(response)
2613
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2615
1031
@needs_write_lock
2616
1032
def set_revision_history(self, rev_history):
2617
1033
# Send just the tip revision of the history; the server will generate
2618
1034
# the full history from that. If the revision doesn't exist in this
2619
1035
# branch, NoSuchRevision will be raised.
1036
path = self.bzrdir._path_for_remote_call(self._client)
2620
1037
if rev_history == []:
2621
1038
rev_id = 'null:'
2623
1040
rev_id = rev_history[-1]
2624
self._set_last_revision(rev_id)
2625
for hook in branch.Branch.hooks['set_rh']:
2626
hook(self, rev_history)
1041
self._clear_cached_state()
1042
response = self._client.call('Branch.set_last_revision',
1043
path, self._lock_token, self._repo_lock_token, rev_id)
1044
if response[0] == 'NoSuchRevision':
1045
raise NoSuchRevision(self, rev_id)
1047
assert response == ('ok',), (
1048
'unexpected response code %r' % (response,))
2627
1049
self._cache_revision_history(rev_history)
2629
def _get_parent_location(self):
2630
medium = self._client._medium
2631
if medium._is_remote_before((1, 13)):
2632
return self._vfs_get_parent_location()
2634
response = self._call('Branch.get_parent', self._remote_path())
2635
except errors.UnknownSmartMethod:
2636
medium._remember_remote_is_before((1, 13))
2637
return self._vfs_get_parent_location()
2638
if len(response) != 1:
2639
raise errors.UnexpectedSmartServerResponse(response)
2640
parent_location = response[0]
2641
if parent_location == '':
2643
return parent_location
2645
def _vfs_get_parent_location(self):
2647
return self._real_branch._get_parent_location()
2649
def _set_parent_location(self, url):
2650
medium = self._client._medium
2651
if medium._is_remote_before((1, 15)):
2652
return self._vfs_set_parent_location(url)
2654
call_url = url or ''
2655
if type(call_url) is not str:
2656
raise AssertionError('url must be a str or None (%s)' % url)
2657
response = self._call('Branch.set_parent_location',
2658
self._remote_path(), self._lock_token, self._repo_lock_token,
2660
except errors.UnknownSmartMethod:
2661
medium._remember_remote_is_before((1, 15))
2662
return self._vfs_set_parent_location(url)
2664
raise errors.UnexpectedSmartServerResponse(response)
2666
def _vfs_set_parent_location(self, url):
2668
return self._real_branch._set_parent_location(url)
1051
def get_parent(self):
1053
return self._real_branch.get_parent()
1055
def set_parent(self, url):
1057
return self._real_branch.set_parent(url)
1059
def get_config(self):
1060
return RemoteBranchConfig(self)
1062
def sprout(self, to_bzrdir, revision_id=None):
1063
# Like Branch.sprout, except that it sprouts a branch in the default
1064
# format, because RemoteBranches can't be created at arbitrary URLs.
1065
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1066
# to_bzrdir.create_branch...
1067
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1068
self.copy_content_into(result, revision_id=revision_id)
1069
result.set_parent(self.bzrdir.root_transport.base)
1073
def append_revision(self, *revision_ids):
1075
return self._real_branch.append_revision(*revision_ids)
2670
1077
@needs_write_lock
2671
1078
def pull(self, source, overwrite=False, stop_revision=None,
2673
self._clear_cached_state_of_remote_branch_only()
1080
# FIXME: This asks the real branch to run the hooks, which means
1081
# they're called with the wrong target branch parameter.
1082
# The test suite specifically allows this at present but it should be
1083
# fixed. It should get a _override_hook_target branch,
1084
# as push does. -- mbp 20070405
2674
1085
self._ensure_real()
2675
return self._real_branch.pull(
1086
self._real_branch.pull(
2676
1087
source, overwrite=overwrite, stop_revision=stop_revision,
2677
_override_hook_target=self, **kwargs)
2679
1090
@needs_read_lock
2680
1091
def push(self, target, overwrite=False, stop_revision=None):
2686
1097
def is_locked(self):
2687
1098
return self._lock_count >= 1
2690
def revision_id_to_revno(self, revision_id):
2692
return self._real_branch.revision_id_to_revno(revision_id)
2695
1100
def set_last_revision_info(self, revno, revision_id):
2696
# XXX: These should be returned by the set_last_revision_info verb
2697
old_revno, old_revid = self.last_revision_info()
2698
self._run_pre_change_branch_tip_hooks(revno, revision_id)
2699
revision_id = ensure_null(revision_id)
2701
response = self._call('Branch.set_last_revision_info',
2702
self._remote_path(), self._lock_token, self._repo_lock_token,
2703
str(revno), revision_id)
2704
except errors.UnknownSmartMethod:
2706
self._clear_cached_state_of_remote_branch_only()
2707
self._real_branch.set_last_revision_info(revno, revision_id)
2708
self._last_revision_info_cache = revno, revision_id
2710
if response == ('ok',):
2711
self._clear_cached_state()
2712
self._last_revision_info_cache = revno, revision_id
2713
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2714
# Update the _real_branch's cache too.
2715
if self._real_branch is not None:
2716
cache = self._last_revision_info_cache
2717
self._real_branch._last_revision_info_cache = cache
2719
raise errors.UnexpectedSmartServerResponse(response)
1102
self._clear_cached_state()
1103
return self._real_branch.set_last_revision_info(revno, revision_id)
2722
1105
def generate_revision_history(self, revision_id, last_rev=None,
2723
1106
other_branch=None):
2724
medium = self._client._medium
2725
if not medium._is_remote_before((1, 6)):
2726
# Use a smart method for 1.6 and above servers
2728
self._set_last_revision_descendant(revision_id, other_branch,
2729
allow_diverged=True, allow_overwrite_descendant=True)
2731
except errors.UnknownSmartMethod:
2732
medium._remember_remote_is_before((1, 6))
2733
self._clear_cached_state_of_remote_branch_only()
2734
self.set_revision_history(self._lefthand_history(revision_id,
2735
last_rev=last_rev,other_branch=other_branch))
1108
return self._real_branch.generate_revision_history(
1109
revision_id, last_rev=last_rev, other_branch=other_branch)
1114
return self._real_branch.tags
2737
1116
def set_push_location(self, location):
2738
1117
self._ensure_real()
2739
1118
return self._real_branch.set_push_location(location)
2742
class RemoteConfig(object):
2743
"""A Config that reads and writes from smart verbs.
2745
It is a low-level object that considers config data to be name/value pairs
2746
that may be associated with a section. Assigning meaning to the these
2747
values is done at higher levels like bzrlib.config.TreeConfig.
2750
def get_option(self, name, section=None, default=None):
2751
"""Return the value associated with a named option.
2753
:param name: The name of the value
2754
:param section: The section the option is in (if any)
2755
:param default: The value to return if the value is not set
2756
:return: The value or default value
2759
configobj = self._get_configobj()
2761
section_obj = configobj
2764
section_obj = configobj[section]
2767
return section_obj.get(name, default)
2768
except errors.UnknownSmartMethod:
2769
return self._vfs_get_option(name, section, default)
2771
def _response_to_configobj(self, response):
2772
if len(response[0]) and response[0][0] != 'ok':
2773
raise errors.UnexpectedSmartServerResponse(response)
2774
lines = response[1].read_body_bytes().splitlines()
2775
return config.ConfigObj(lines, encoding='utf-8')
2778
class RemoteBranchConfig(RemoteConfig):
2779
"""A RemoteConfig for Branches."""
2781
def __init__(self, branch):
2782
self._branch = branch
2784
def _get_configobj(self):
2785
path = self._branch._remote_path()
2786
response = self._branch._client.call_expecting_body(
2787
'Branch.get_config_file', path)
2788
return self._response_to_configobj(response)
2790
def set_option(self, value, name, section=None):
2791
"""Set the value associated with a named option.
2793
:param value: The value to set
2794
:param name: The name of the value to set
2795
:param section: The section the option is in (if any)
2797
medium = self._branch._client._medium
2798
if medium._is_remote_before((1, 14)):
2799
return self._vfs_set_option(value, name, section)
2800
if isinstance(value, dict):
2801
if medium._is_remote_before((2, 2)):
2802
return self._vfs_set_option(value, name, section)
2803
return self._set_config_option_dict(value, name, section)
2805
return self._set_config_option(value, name, section)
2807
def _set_config_option(self, value, name, section):
2809
path = self._branch._remote_path()
2810
response = self._branch._client.call('Branch.set_config_option',
2811
path, self._branch._lock_token, self._branch._repo_lock_token,
2812
value.encode('utf8'), name, section or '')
2813
except errors.UnknownSmartMethod:
2814
medium = self._branch._client._medium
2815
medium._remember_remote_is_before((1, 14))
2816
return self._vfs_set_option(value, name, section)
2818
raise errors.UnexpectedSmartServerResponse(response)
2820
def _serialize_option_dict(self, option_dict):
2822
for key, value in option_dict.items():
2823
if isinstance(key, unicode):
2824
key = key.encode('utf8')
2825
if isinstance(value, unicode):
2826
value = value.encode('utf8')
2827
utf8_dict[key] = value
2828
return bencode.bencode(utf8_dict)
2830
def _set_config_option_dict(self, value, name, section):
2832
path = self._branch._remote_path()
2833
serialised_dict = self._serialize_option_dict(value)
2834
response = self._branch._client.call(
2835
'Branch.set_config_option_dict',
2836
path, self._branch._lock_token, self._branch._repo_lock_token,
2837
serialised_dict, name, section or '')
2838
except errors.UnknownSmartMethod:
2839
medium = self._branch._client._medium
2840
medium._remember_remote_is_before((2, 2))
2841
return self._vfs_set_option(value, name, section)
2843
raise errors.UnexpectedSmartServerResponse(response)
2845
def _real_object(self):
2846
self._branch._ensure_real()
2847
return self._branch._real_branch
2849
def _vfs_set_option(self, value, name, section=None):
2850
return self._real_object()._get_config().set_option(
2851
value, name, section)
2854
class RemoteBzrDirConfig(RemoteConfig):
2855
"""A RemoteConfig for BzrDirs."""
2857
def __init__(self, bzrdir):
2858
self._bzrdir = bzrdir
2860
def _get_configobj(self):
2861
medium = self._bzrdir._client._medium
2862
verb = 'BzrDir.get_config_file'
2863
if medium._is_remote_before((1, 15)):
2864
raise errors.UnknownSmartMethod(verb)
2865
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
2866
response = self._bzrdir._call_expecting_body(
2868
return self._response_to_configobj(response)
2870
def _vfs_get_option(self, name, section, default):
2871
return self._real_object()._get_config().get_option(
2872
name, section, default)
2874
def set_option(self, value, name, section=None):
2875
"""Set the value associated with a named option.
2877
:param value: The value to set
2878
:param name: The name of the value to set
2879
:param section: The section the option is in (if any)
2881
return self._real_object()._get_config().set_option(
2882
value, name, section)
2884
def _real_object(self):
2885
self._bzrdir._ensure_real()
2886
return self._bzrdir._real_bzrdir
1120
def update_revisions(self, other, stop_revision=None):
1122
return self._real_branch.update_revisions(
1123
other, stop_revision=stop_revision)
1126
class RemoteBranchConfig(BranchConfig):
1129
self.branch._ensure_real()
1130
return self.branch._real_branch.get_config().username()
1132
def _get_branch_data_config(self):
1133
self.branch._ensure_real()
1134
if self._branch_data_config is None:
1135
self._branch_data_config = TreeConfig(self.branch._real_branch)
1136
return self._branch_data_config
2890
1139
def _extract_tar(tar, to_dir):
2895
1144
for tarinfo in tar:
2896
1145
tar.extract(tarinfo, to_dir)
2899
def _translate_error(err, **context):
2900
"""Translate an ErrorFromSmartServer into a more useful error.
2902
Possible context keys:
2910
If the error from the server doesn't match a known pattern, then
2911
UnknownErrorFromSmartServer is raised.
2915
return context[name]
2916
except KeyError, key_err:
2917
mutter('Missing key %r in context %r', key_err.args[0], context)
2920
"""Get the path from the context if present, otherwise use first error
2924
return context['path']
2925
except KeyError, key_err:
2927
return err.error_args[0]
2928
except IndexError, idx_err:
2930
'Missing key %r in context %r', key_err.args[0], context)
2933
if err.error_verb == 'IncompatibleRepositories':
2934
raise errors.IncompatibleRepositories(err.error_args[0],
2935
err.error_args[1], err.error_args[2])
2936
elif err.error_verb == 'NoSuchRevision':
2937
raise NoSuchRevision(find('branch'), err.error_args[0])
2938
elif err.error_verb == 'nosuchrevision':
2939
raise NoSuchRevision(find('repository'), err.error_args[0])
2940
elif err.error_verb == 'nobranch':
2941
if len(err.error_args) >= 1:
2942
extra = err.error_args[0]
2945
raise errors.NotBranchError(path=find('bzrdir').root_transport.base,
2947
elif err.error_verb == 'norepository':
2948
raise errors.NoRepositoryPresent(find('bzrdir'))
2949
elif err.error_verb == 'LockContention':
2950
raise errors.LockContention('(remote lock)')
2951
elif err.error_verb == 'UnlockableTransport':
2952
raise errors.UnlockableTransport(find('bzrdir').root_transport)
2953
elif err.error_verb == 'LockFailed':
2954
raise errors.LockFailed(err.error_args[0], err.error_args[1])
2955
elif err.error_verb == 'TokenMismatch':
2956
raise errors.TokenMismatch(find('token'), '(remote token)')
2957
elif err.error_verb == 'Diverged':
2958
raise errors.DivergedBranches(find('branch'), find('other_branch'))
2959
elif err.error_verb == 'TipChangeRejected':
2960
raise errors.TipChangeRejected(err.error_args[0].decode('utf8'))
2961
elif err.error_verb == 'UnstackableBranchFormat':
2962
raise errors.UnstackableBranchFormat(*err.error_args)
2963
elif err.error_verb == 'UnstackableRepositoryFormat':
2964
raise errors.UnstackableRepositoryFormat(*err.error_args)
2965
elif err.error_verb == 'NotStacked':
2966
raise errors.NotStacked(branch=find('branch'))
2967
elif err.error_verb == 'PermissionDenied':
2969
if len(err.error_args) >= 2:
2970
extra = err.error_args[1]
2973
raise errors.PermissionDenied(path, extra=extra)
2974
elif err.error_verb == 'ReadError':
2976
raise errors.ReadError(path)
2977
elif err.error_verb == 'NoSuchFile':
2979
raise errors.NoSuchFile(path)
2980
elif err.error_verb == 'FileExists':
2981
raise errors.FileExists(err.error_args[0])
2982
elif err.error_verb == 'DirectoryNotEmpty':
2983
raise errors.DirectoryNotEmpty(err.error_args[0])
2984
elif err.error_verb == 'ShortReadvError':
2985
args = err.error_args
2986
raise errors.ShortReadvError(
2987
args[0], int(args[1]), int(args[2]), int(args[3]))
2988
elif err.error_verb in ('UnicodeEncodeError', 'UnicodeDecodeError'):
2989
encoding = str(err.error_args[0]) # encoding must always be a string
2990
val = err.error_args[1]
2991
start = int(err.error_args[2])
2992
end = int(err.error_args[3])
2993
reason = str(err.error_args[4]) # reason must always be a string
2994
if val.startswith('u:'):
2995
val = val[2:].decode('utf-8')
2996
elif val.startswith('s:'):
2997
val = val[2:].decode('base64')
2998
if err.error_verb == 'UnicodeDecodeError':
2999
raise UnicodeDecodeError(encoding, val, start, end, reason)
3000
elif err.error_verb == 'UnicodeEncodeError':
3001
raise UnicodeEncodeError(encoding, val, start, end, reason)
3002
elif err.error_verb == 'ReadOnlyError':
3003
raise errors.TransportNotPossible('readonly transport')
3004
raise errors.UnknownErrorFromSmartServer(err)