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
17
from __future__ import absolute_import
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
20
from cStringIO import StringIO
22
22
from bzrlib import (
25
bzrdir as _mod_bzrdir,
37
repository as _mod_repository,
38
revision as _mod_revision,
41
testament as _mod_testament,
46
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
47
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
48
from bzrlib.errors import (
52
from bzrlib.i18n import gettext
53
from bzrlib.inventory import Inventory
28
from bzrlib.branch import Branch, BranchReferenceFormat
29
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
30
from bzrlib.config import BranchConfig, TreeConfig
31
from bzrlib.decorators import needs_read_lock, needs_write_lock
32
from bzrlib.errors import NoSuchRevision
54
33
from bzrlib.lockable_files import LockableFiles
55
from bzrlib.smart import client, vfs, repository as smart_repo
56
from bzrlib.smart.client import _SmartClient
57
34
from bzrlib.revision import NULL_REVISION
58
from bzrlib.revisiontree import InventoryRevisionTree
59
from bzrlib.repository import RepositoryWriteLockResult, _LazyListJoin
60
from bzrlib.serializer import format_registry as serializer_format_registry
61
from bzrlib.trace import mutter, note, warning, log_exception_quietly
64
_DEFAULT_SEARCH_DEPTH = 100
67
class _RpcHelper(object):
68
"""Mixin class that helps with issuing RPCs."""
70
def _call(self, method, *args, **err_context):
72
return self._client.call(method, *args)
73
except errors.ErrorFromSmartServer, err:
74
self._translate_error(err, **err_context)
76
def _call_expecting_body(self, method, *args, **err_context):
78
return self._client.call_expecting_body(method, *args)
79
except errors.ErrorFromSmartServer, err:
80
self._translate_error(err, **err_context)
82
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
84
return self._client.call_with_body_bytes(method, args, body_bytes)
85
except errors.ErrorFromSmartServer, err:
86
self._translate_error(err, **err_context)
88
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
91
return self._client.call_with_body_bytes_expecting_body(
92
method, args, body_bytes)
93
except errors.ErrorFromSmartServer, err:
94
self._translate_error(err, **err_context)
97
def response_tuple_to_repo_format(response):
98
"""Convert a response tuple describing a repository format to a format."""
99
format = RemoteRepositoryFormat()
100
format._rich_root_data = (response[0] == 'yes')
101
format._supports_tree_reference = (response[1] == 'yes')
102
format._supports_external_lookups = (response[2] == 'yes')
103
format._network_name = response[3]
107
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
108
# does not have to be imported unless a remote format is involved.
110
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
111
"""Format representing bzrdirs accessed via a smart server"""
113
supports_workingtrees = False
116
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
117
# XXX: It's a bit ugly that the network name is here, because we'd
118
# like to believe that format objects are stateless or at least
119
# immutable, However, we do at least avoid mutating the name after
120
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
121
self._network_name = None
124
return "%s(_network_name=%r)" % (self.__class__.__name__,
127
def get_format_description(self):
128
if self._network_name:
130
real_format = controldir.network_format_registry.get(
135
return 'Remote: ' + real_format.get_format_description()
136
return 'bzr remote bzrdir'
138
def get_format_string(self):
139
raise NotImplementedError(self.get_format_string)
141
def network_name(self):
142
if self._network_name:
143
return self._network_name
145
raise AssertionError("No network name set.")
147
def initialize_on_transport(self, transport):
149
# hand off the request to the smart server
150
client_medium = transport.get_smart_medium()
151
except errors.NoSmartMedium:
152
# TODO: lookup the local format from a server hint.
153
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
154
return local_dir_format.initialize_on_transport(transport)
155
client = _SmartClient(client_medium)
156
path = client.remote_path_from_transport(transport)
158
response = client.call('BzrDirFormat.initialize', path)
159
except errors.ErrorFromSmartServer, err:
160
_translate_error(err, path=path)
161
if response[0] != 'ok':
162
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
163
format = RemoteBzrDirFormat()
164
self._supply_sub_formats_to(format)
165
return RemoteBzrDir(transport, format)
167
def parse_NoneTrueFalse(self, arg):
174
raise AssertionError("invalid arg %r" % arg)
176
def _serialize_NoneTrueFalse(self, arg):
183
def _serialize_NoneString(self, arg):
186
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
187
create_prefix=False, force_new_repo=False, stacked_on=None,
188
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
191
# hand off the request to the smart server
192
client_medium = transport.get_smart_medium()
193
except errors.NoSmartMedium:
196
# Decline to open it if the server doesn't support our required
197
# version (3) so that the VFS-based transport will do it.
198
if client_medium.should_probe():
200
server_version = client_medium.protocol_version()
201
if server_version != '2':
205
except errors.SmartProtocolError:
206
# Apparently there's no usable smart server there, even though
207
# the medium supports the smart protocol.
212
client = _SmartClient(client_medium)
213
path = client.remote_path_from_transport(transport)
214
if client_medium._is_remote_before((1, 16)):
217
# TODO: lookup the local format from a server hint.
218
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
219
self._supply_sub_formats_to(local_dir_format)
220
return local_dir_format.initialize_on_transport_ex(transport,
221
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
222
force_new_repo=force_new_repo, stacked_on=stacked_on,
223
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
224
make_working_trees=make_working_trees, shared_repo=shared_repo,
226
return self._initialize_on_transport_ex_rpc(client, path, transport,
227
use_existing_dir, create_prefix, force_new_repo, stacked_on,
228
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
230
def _initialize_on_transport_ex_rpc(self, client, path, transport,
231
use_existing_dir, create_prefix, force_new_repo, stacked_on,
232
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
234
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
235
args.append(self._serialize_NoneTrueFalse(create_prefix))
236
args.append(self._serialize_NoneTrueFalse(force_new_repo))
237
args.append(self._serialize_NoneString(stacked_on))
238
# stack_on_pwd is often/usually our transport
241
stack_on_pwd = transport.relpath(stack_on_pwd)
244
except errors.PathNotChild:
246
args.append(self._serialize_NoneString(stack_on_pwd))
247
args.append(self._serialize_NoneString(repo_format_name))
248
args.append(self._serialize_NoneTrueFalse(make_working_trees))
249
args.append(self._serialize_NoneTrueFalse(shared_repo))
250
request_network_name = self._network_name or \
251
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
253
response = client.call('BzrDirFormat.initialize_ex_1.16',
254
request_network_name, path, *args)
255
except errors.UnknownSmartMethod:
256
client._medium._remember_remote_is_before((1,16))
257
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
258
self._supply_sub_formats_to(local_dir_format)
259
return local_dir_format.initialize_on_transport_ex(transport,
260
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
261
force_new_repo=force_new_repo, stacked_on=stacked_on,
262
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
263
make_working_trees=make_working_trees, shared_repo=shared_repo,
265
except errors.ErrorFromSmartServer, err:
266
_translate_error(err, path=path)
267
repo_path = response[0]
268
bzrdir_name = response[6]
269
require_stacking = response[7]
270
require_stacking = self.parse_NoneTrueFalse(require_stacking)
271
format = RemoteBzrDirFormat()
272
format._network_name = bzrdir_name
273
self._supply_sub_formats_to(format)
274
bzrdir = RemoteBzrDir(transport, format, _client=client)
276
repo_format = response_tuple_to_repo_format(response[1:])
280
repo_bzrdir_format = RemoteBzrDirFormat()
281
repo_bzrdir_format._network_name = response[5]
282
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
286
final_stack = response[8] or None
287
final_stack_pwd = response[9] or None
289
final_stack_pwd = urlutils.join(
290
transport.base, final_stack_pwd)
291
remote_repo = RemoteRepository(repo_bzr, repo_format)
292
if len(response) > 10:
293
# Updated server verb that locks remotely.
294
repo_lock_token = response[10] or None
295
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
297
remote_repo.dont_leave_lock_in_place()
299
remote_repo.lock_write()
300
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
301
final_stack_pwd, require_stacking)
302
policy.acquire_repository()
306
bzrdir._format.set_branch_format(self.get_branch_format())
308
# The repo has already been created, but we need to make sure that
309
# we'll make a stackable branch.
310
bzrdir._format.require_stacking(_skip_repo=True)
311
return remote_repo, bzrdir, require_stacking, policy
313
def _open(self, transport):
314
return RemoteBzrDir(transport, self)
316
def __eq__(self, other):
317
if not isinstance(other, RemoteBzrDirFormat):
319
return self.get_format_description() == other.get_format_description()
321
def __return_repository_format(self):
322
# Always return a RemoteRepositoryFormat object, but if a specific bzr
323
# repository format has been asked for, tell the RemoteRepositoryFormat
324
# that it should use that for init() etc.
325
result = RemoteRepositoryFormat()
326
custom_format = getattr(self, '_repository_format', None)
328
if isinstance(custom_format, RemoteRepositoryFormat):
331
# We will use the custom format to create repositories over the
332
# wire; expose its details like rich_root_data for code to
334
result._custom_format = custom_format
337
def get_branch_format(self):
338
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
339
if not isinstance(result, RemoteBranchFormat):
340
new_result = RemoteBranchFormat()
341
new_result._custom_format = result
343
self.set_branch_format(new_result)
347
repository_format = property(__return_repository_format,
348
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
351
class RemoteControlStore(config.IniFileStore):
352
"""Control store which attempts to use HPSS calls to retrieve control store.
354
Note that this is specific to bzr-based formats.
357
def __init__(self, bzrdir):
358
super(RemoteControlStore, self).__init__()
360
self._real_store = None
362
def lock_write(self, token=None):
364
return self._real_store.lock_write(token)
368
return self._real_store.unlock()
372
# We need to be able to override the undecorated implementation
373
self.save_without_locking()
375
def save_without_locking(self):
376
super(RemoteControlStore, self).save()
378
def _ensure_real(self):
379
self.bzrdir._ensure_real()
380
if self._real_store is None:
381
self._real_store = config.ControlStore(self.bzrdir)
383
def external_url(self):
384
return self.bzrdir.user_url
386
def _load_content(self):
387
medium = self.bzrdir._client._medium
388
path = self.bzrdir._path_for_remote_call(self.bzrdir._client)
390
response, handler = self.bzrdir._call_expecting_body(
391
'BzrDir.get_config_file', path)
392
except errors.UnknownSmartMethod:
394
return self._real_store._load_content()
395
if len(response) and response[0] != 'ok':
396
raise errors.UnexpectedSmartServerResponse(response)
397
return handler.read_body_bytes()
399
def _save_content(self, content):
400
# FIXME JRV 2011-11-22: Ideally this should use a
401
# HPSS call too, but at the moment it is not possible
402
# to write lock control directories.
404
return self._real_store._save_content(content)
407
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
35
from bzrlib.smart import client, vfs
36
from bzrlib.trace import note
38
# Note: RemoteBzrDirFormat is in bzrdir.py
40
class RemoteBzrDir(BzrDir):
408
41
"""Control directory on a remote server, accessed via bzr:// or similar."""
410
def __init__(self, transport, format, _client=None, _force_probe=False):
43
def __init__(self, transport, _client=None):
411
44
"""Construct a RemoteBzrDir.
413
46
:param _client: Private parameter for testing. Disables probing and the
414
47
use of a real bzrdir.
416
_mod_bzrdir.BzrDir.__init__(self, transport, format)
49
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
417
50
# this object holds a delegated bzrdir that uses file-level operations
418
51
# to talk to the other side
419
52
self._real_bzrdir = None
420
self._has_working_tree = None
421
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
422
# create_branch for details.
423
self._next_open_branch_result = None
425
54
if _client is None:
426
medium = transport.get_smart_medium()
427
self._client = client._SmartClient(medium)
55
self._medium = transport.get_smart_client()
56
self._client = client._SmartClient(self._medium)
429
58
self._client = _client
436
return '%s(%r)' % (self.__class__.__name__, self._client)
438
def _probe_bzrdir(self):
439
medium = self._client._medium
440
62
path = self._path_for_remote_call(self._client)
441
if medium._is_remote_before((2, 1)):
445
self._rpc_open_2_1(path)
447
except errors.UnknownSmartMethod:
448
medium._remember_remote_is_before((2, 1))
451
def _rpc_open_2_1(self, path):
452
response = self._call('BzrDir.open_2.1', path)
453
if response == ('no',):
454
raise errors.NotBranchError(path=self.root_transport.base)
455
elif response[0] == 'yes':
456
if response[1] == 'yes':
457
self._has_working_tree = True
458
elif response[1] == 'no':
459
self._has_working_tree = False
461
raise errors.UnexpectedSmartServerResponse(response)
463
raise errors.UnexpectedSmartServerResponse(response)
465
def _rpc_open(self, path):
466
response = self._call('BzrDir.open', path)
63
response = self._client.call('BzrDir.open', path)
467
64
if response not in [('yes',), ('no',)]:
468
65
raise errors.UnexpectedSmartServerResponse(response)
469
66
if response == ('no',):
470
raise errors.NotBranchError(path=self.root_transport.base)
67
raise errors.NotBranchError(path=transport.base)
472
69
def _ensure_real(self):
473
70
"""Ensure that there is a _real_bzrdir set.
475
72
Used before calls to self._real_bzrdir.
477
74
if not self._real_bzrdir:
478
if 'hpssvfs' in debug.debug_flags:
480
warning('VFS BzrDir access triggered\n%s',
481
''.join(traceback.format_stack()))
482
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
75
self._real_bzrdir = BzrDir.open_from_transport(
483
76
self.root_transport, _server_formats=False)
484
self._format._network_name = \
485
self._real_bzrdir._format.network_name()
487
def _translate_error(self, err, **context):
488
_translate_error(err, bzrdir=self, **context)
490
def break_lock(self):
491
# Prevent aliasing problems in the next_open_branch_result cache.
492
# See create_branch for rationale.
493
self._next_open_branch_result = None
494
return _mod_bzrdir.BzrDir.break_lock(self)
496
def _vfs_checkout_metadir(self):
498
return self._real_bzrdir.checkout_metadir()
500
def checkout_metadir(self):
501
"""Retrieve the controldir format to use for checkouts of this one.
503
medium = self._client._medium
504
if medium._is_remote_before((2, 5)):
505
return self._vfs_checkout_metadir()
506
path = self._path_for_remote_call(self._client)
508
response = self._client.call('BzrDir.checkout_metadir',
510
except errors.UnknownSmartMethod:
511
medium._remember_remote_is_before((2, 5))
512
return self._vfs_checkout_metadir()
513
if len(response) != 3:
514
raise errors.UnexpectedSmartServerResponse(response)
515
control_name, repo_name, branch_name = response
517
format = controldir.network_format_registry.get(control_name)
519
raise errors.UnknownFormatError(kind='control',
523
repo_format = _mod_repository.network_format_registry.get(
526
raise errors.UnknownFormatError(kind='repository',
528
format.repository_format = repo_format
531
format.set_branch_format(
532
branch.network_format_registry.get(branch_name))
534
raise errors.UnknownFormatError(kind='branch',
538
def _vfs_cloning_metadir(self, require_stacking=False):
540
return self._real_bzrdir.cloning_metadir(
541
require_stacking=require_stacking)
543
def cloning_metadir(self, require_stacking=False):
544
medium = self._client._medium
545
if medium._is_remote_before((1, 13)):
546
return self._vfs_cloning_metadir(require_stacking=require_stacking)
547
verb = 'BzrDir.cloning_metadir'
552
path = self._path_for_remote_call(self._client)
554
response = self._call(verb, path, stacking)
555
except errors.UnknownSmartMethod:
556
medium._remember_remote_is_before((1, 13))
557
return self._vfs_cloning_metadir(require_stacking=require_stacking)
558
except errors.UnknownErrorFromSmartServer, err:
559
if err.error_tuple != ('BranchReference',):
561
# We need to resolve the branch reference to determine the
562
# cloning_metadir. This causes unnecessary RPCs to open the
563
# referenced branch (and bzrdir, etc) but only when the caller
564
# didn't already resolve the branch reference.
565
referenced_branch = self.open_branch()
566
return referenced_branch.bzrdir.cloning_metadir()
567
if len(response) != 3:
568
raise errors.UnexpectedSmartServerResponse(response)
569
control_name, repo_name, branch_info = response
570
if len(branch_info) != 2:
571
raise errors.UnexpectedSmartServerResponse(response)
572
branch_ref, branch_name = branch_info
574
format = controldir.network_format_registry.get(control_name)
576
raise errors.UnknownFormatError(kind='control', format=control_name)
580
format.repository_format = _mod_repository.network_format_registry.get(
583
raise errors.UnknownFormatError(kind='repository',
585
if branch_ref == 'ref':
586
# XXX: we need possible_transports here to avoid reopening the
587
# connection to the referenced location
588
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
589
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
590
format.set_branch_format(branch_format)
591
elif branch_ref == 'branch':
594
branch_format = branch.network_format_registry.get(
597
raise errors.UnknownFormatError(kind='branch',
599
format.set_branch_format(branch_format)
601
raise errors.UnexpectedSmartServerResponse(response)
604
78
def create_repository(self, shared=False):
605
# as per meta1 formats - just delegate to the format object which may
607
result = self._format.repository_format.initialize(self, shared)
608
if not isinstance(result, RemoteRepository):
609
return self.open_repository()
613
def destroy_repository(self):
614
"""See BzrDir.destroy_repository"""
615
path = self._path_for_remote_call(self._client)
617
response = self._call('BzrDir.destroy_repository', path)
618
except errors.UnknownSmartMethod:
620
self._real_bzrdir.destroy_repository()
622
if response[0] != 'ok':
623
raise SmartProtocolError('unexpected response code %s' % (response,))
625
def create_branch(self, name=None, repository=None,
626
append_revisions_only=None):
627
# as per meta1 formats - just delegate to the format object which may
629
real_branch = self._format.get_branch_format().initialize(self,
630
name=name, repository=repository,
631
append_revisions_only=append_revisions_only)
632
if not isinstance(real_branch, RemoteBranch):
633
if not isinstance(repository, RemoteRepository):
634
raise AssertionError(
635
'need a RemoteRepository to use with RemoteBranch, got %r'
637
result = RemoteBranch(self, repository, real_branch, name=name)
640
# BzrDir.clone_on_transport() uses the result of create_branch but does
641
# not return it to its callers; we save approximately 8% of our round
642
# trips by handing the branch we created back to the first caller to
643
# open_branch rather than probing anew. Long term we need a API in
644
# bzrdir that doesn't discard result objects (like result_branch).
646
self._next_open_branch_result = result
649
def destroy_branch(self, name=None):
650
"""See BzrDir.destroy_branch"""
651
path = self._path_for_remote_call(self._client)
657
response = self._call('BzrDir.destroy_branch', path, *args)
658
except errors.UnknownSmartMethod:
660
self._real_bzrdir.destroy_branch(name=name)
661
self._next_open_branch_result = None
663
self._next_open_branch_result = None
664
if response[0] != 'ok':
665
raise SmartProtocolError('unexpected response code %s' % (response,))
667
def create_workingtree(self, revision_id=None, from_branch=None,
668
accelerator_tree=None, hardlink=False):
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):
669
89
raise errors.NotLocalUrl(self.transport.base)
671
def find_branch_format(self, name=None):
91
def find_branch_format(self):
672
92
"""Find the branch 'format' for this bzrdir.
674
94
This might be a synthetic object for e.g. RemoteBranch and SVN.
676
b = self.open_branch(name=name)
96
b = self.open_branch()
679
def get_branch_reference(self, name=None):
99
def get_branch_reference(self):
680
100
"""See BzrDir.get_branch_reference()."""
682
# XXX JRV20100304: Support opening colocated branches
683
raise errors.NoColocatedBranchSupport(self)
684
response = self._get_branch_reference()
685
if response[0] == 'ref':
690
def _get_branch_reference(self):
691
101
path = self._path_for_remote_call(self._client)
692
medium = self._client._medium
694
('BzrDir.open_branchV3', (2, 1)),
695
('BzrDir.open_branchV2', (1, 13)),
696
('BzrDir.open_branch', None),
698
for verb, required_version in candidate_calls:
699
if required_version and medium._is_remote_before(required_version):
702
response = self._call(verb, path)
703
except errors.UnknownSmartMethod:
704
if required_version is None:
706
medium._remember_remote_is_before(required_version)
709
if verb == 'BzrDir.open_branch':
710
if response[0] != 'ok':
711
raise errors.UnexpectedSmartServerResponse(response)
712
if response[1] != '':
713
return ('ref', response[1])
715
return ('branch', '')
716
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)
717
113
raise errors.UnexpectedSmartServerResponse(response)
720
def _get_tree_branch(self, name=None):
721
"""See BzrDir._get_tree_branch()."""
722
return None, self.open_branch(name=name)
724
def open_branch(self, name=None, unsupported=False,
725
ignore_fallbacks=False, possible_transports=None):
727
raise NotImplementedError('unsupported flag support not implemented yet.')
728
if self._next_open_branch_result is not None:
729
# See create_branch for details.
730
result = self._next_open_branch_result
731
self._next_open_branch_result = None
733
response = self._get_branch_reference()
734
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())
735
122
# a branch reference, use the existing BranchReference logic.
736
123
format = BranchReferenceFormat()
737
return format.open(self, name=name, _found=True,
738
location=response[1], ignore_fallbacks=ignore_fallbacks,
739
possible_transports=possible_transports)
740
branch_format_name = response[1]
741
if not branch_format_name:
742
branch_format_name = None
743
format = RemoteBranchFormat(network_name=branch_format_name)
744
return RemoteBranch(self, self.find_repository(), format=format,
745
setup_stacking=not ignore_fallbacks, name=name,
746
possible_transports=possible_transports)
748
def _open_repo_v1(self, path):
749
verb = 'BzrDir.find_repository'
750
response = self._call(verb, path)
751
if response[0] != 'ok':
752
raise errors.UnexpectedSmartServerResponse(response)
753
# servers that only support the v1 method don't support external
756
repo = self._real_bzrdir.open_repository()
757
response = response + ('no', repo._format.network_name())
758
return response, repo
760
def _open_repo_v2(self, path):
761
verb = 'BzrDir.find_repositoryV2'
762
response = self._call(verb, path)
763
if response[0] != 'ok':
764
raise errors.UnexpectedSmartServerResponse(response)
766
repo = self._real_bzrdir.open_repository()
767
response = response + (repo._format.network_name(),)
768
return response, repo
770
def _open_repo_v3(self, path):
771
verb = 'BzrDir.find_repositoryV3'
772
medium = self._client._medium
773
if medium._is_remote_before((1, 13)):
774
raise errors.UnknownSmartMethod(verb)
776
response = self._call(verb, path)
777
except errors.UnknownSmartMethod:
778
medium._remember_remote_is_before((1, 13))
780
if response[0] != 'ok':
781
raise errors.UnexpectedSmartServerResponse(response)
782
return response, None
124
return format.open(self, _found=True, location=reference_url)
784
126
def open_repository(self):
785
127
path = self._path_for_remote_call(self._client)
787
for probe in [self._open_repo_v3, self._open_repo_v2,
790
response, real_repo = probe(path)
792
except errors.UnknownSmartMethod:
795
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
796
if response[0] != 'ok':
797
raise errors.UnexpectedSmartServerResponse(response)
798
if len(response) != 6:
799
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,)
800
134
if response[1] == '':
801
# repo is at this dir.
802
format = response_tuple_to_repo_format(response[2:])
803
# Used to support creating a real format instance when needed.
804
format._creating_bzrdir = self
805
remote_repo = RemoteRepository(self, format)
806
format._creating_repo = remote_repo
807
if real_repo is not None:
808
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)
811
140
raise errors.NoRepositoryPresent(self)
813
def has_workingtree(self):
814
if self._has_working_tree is None:
815
path = self._path_for_remote_call(self._client)
817
response = self._call('BzrDir.has_workingtree', path)
818
except errors.UnknownSmartMethod:
820
self._has_working_tree = self._real_bzrdir.has_workingtree()
822
if response[0] not in ('yes', 'no'):
823
raise SmartProtocolError('unexpected response code %s' % (response,))
824
self._has_working_tree = (response[0] == 'yes')
825
return self._has_working_tree
827
142
def open_workingtree(self, recommend_upgrade=True):
828
if self.has_workingtree():
144
if self._real_bzrdir.has_workingtree():
829
145
raise errors.NotLocalUrl(self.root_transport)
831
147
raise errors.NoWorkingTree(self.root_transport.base)
833
149
def _path_for_remote_call(self, client):
834
150
"""Return the path to be used for this bzrdir in a remote call."""
835
return urlutils.split_segment_parameters_raw(
836
client.remote_path_from_transport(self.root_transport))[0]
151
return client.remote_path_from_transport(self.root_transport)
838
def get_branch_transport(self, branch_format, name=None):
153
def get_branch_transport(self, branch_format):
839
154
self._ensure_real()
840
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
155
return self._real_bzrdir.get_branch_transport(branch_format)
842
157
def get_repository_transport(self, repository_format):
843
158
self._ensure_real()
851
166
"""Upgrading of remote bzrdirs is not supported yet."""
854
def needs_format_conversion(self, format):
169
def needs_format_conversion(self, format=None):
855
170
"""Upgrading of remote bzrdirs is not supported yet."""
858
def _get_config(self):
859
return RemoteBzrDirConfig(self)
861
def _get_config_store(self):
862
return RemoteControlStore(self)
865
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
173
def clone(self, url, revision_id=None, force_new_repo=False):
175
return self._real_bzrdir.clone(url, revision_id=revision_id,
176
force_new_repo=force_new_repo)
179
class RemoteRepositoryFormat(repository.RepositoryFormat):
866
180
"""Format for repositories accessed over a _SmartClient.
868
182
Instances of this repository are represented by RemoteRepository
871
The RemoteRepositoryFormat is parameterized during construction
185
The RemoteRepositoryFormat is parameterised during construction
872
186
to reflect the capabilities of the real, remote format. Specifically
873
187
the attributes rich_root_data and supports_tree_reference are set
874
188
on a per instance basis, and are not set (and should not be) at
877
:ivar _custom_format: If set, a specific concrete repository format that
878
will be used when initializing a repository with this
879
RemoteRepositoryFormat.
880
:ivar _creating_repo: If set, the repository object that this
881
RemoteRepositoryFormat was created for: it can be called into
882
to obtain data like the network name.
885
_matchingbzrdir = RemoteBzrDirFormat()
886
supports_full_versioned_files = True
887
supports_leaving_lock = True
890
_mod_repository.RepositoryFormat.__init__(self)
891
self._custom_format = None
892
self._network_name = None
893
self._creating_bzrdir = None
894
self._revision_graph_can_have_wrong_parents = None
895
self._supports_chks = None
896
self._supports_external_lookups = None
897
self._supports_tree_reference = None
898
self._supports_funky_characters = None
899
self._supports_nesting_repositories = None
900
self._rich_root_data = None
903
return "%s(_network_name=%r)" % (self.__class__.__name__,
907
def fast_deltas(self):
909
return self._custom_format.fast_deltas
912
def rich_root_data(self):
913
if self._rich_root_data is None:
915
self._rich_root_data = self._custom_format.rich_root_data
916
return self._rich_root_data
919
def supports_chks(self):
920
if self._supports_chks is None:
922
self._supports_chks = self._custom_format.supports_chks
923
return self._supports_chks
926
def supports_external_lookups(self):
927
if self._supports_external_lookups is None:
929
self._supports_external_lookups = \
930
self._custom_format.supports_external_lookups
931
return self._supports_external_lookups
934
def supports_funky_characters(self):
935
if self._supports_funky_characters is None:
937
self._supports_funky_characters = \
938
self._custom_format.supports_funky_characters
939
return self._supports_funky_characters
942
def supports_nesting_repositories(self):
943
if self._supports_nesting_repositories is None:
945
self._supports_nesting_repositories = \
946
self._custom_format.supports_nesting_repositories
947
return self._supports_nesting_repositories
950
def supports_tree_reference(self):
951
if self._supports_tree_reference is None:
953
self._supports_tree_reference = \
954
self._custom_format.supports_tree_reference
955
return self._supports_tree_reference
958
def revision_graph_can_have_wrong_parents(self):
959
if self._revision_graph_can_have_wrong_parents is None:
961
self._revision_graph_can_have_wrong_parents = \
962
self._custom_format.revision_graph_can_have_wrong_parents
963
return self._revision_graph_can_have_wrong_parents
965
def _vfs_initialize(self, a_bzrdir, shared):
966
"""Helper for common code in initialize."""
967
if self._custom_format:
968
# Custom format requested
969
result = self._custom_format.initialize(a_bzrdir, shared=shared)
970
elif self._creating_bzrdir is not None:
971
# Use the format that the repository we were created to back
973
prior_repo = self._creating_bzrdir.open_repository()
974
prior_repo._ensure_real()
975
result = prior_repo._real_repository._format.initialize(
976
a_bzrdir, shared=shared)
978
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
979
# support remote initialization.
980
# We delegate to a real object at this point (as RemoteBzrDir
981
# delegate to the repository format which would lead to infinite
982
# recursion if we just called a_bzrdir.create_repository.
983
a_bzrdir._ensure_real()
984
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
985
if not isinstance(result, RemoteRepository):
986
return self.open(a_bzrdir)
192
_matchingbzrdir = RemoteBzrDirFormat
990
194
def initialize(self, a_bzrdir, shared=False):
991
# Being asked to create on a non RemoteBzrDir:
992
if not isinstance(a_bzrdir, RemoteBzrDir):
993
return self._vfs_initialize(a_bzrdir, shared)
994
medium = a_bzrdir._client._medium
995
if medium._is_remote_before((1, 13)):
996
return self._vfs_initialize(a_bzrdir, shared)
997
# Creating on a remote bzr dir.
998
# 1) get the network name to use.
999
if self._custom_format:
1000
network_name = self._custom_format.network_name()
1001
elif self._network_name:
1002
network_name = self._network_name
1004
# Select the current bzrlib default and ask for that.
1005
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
1006
reference_format = reference_bzrdir_format.repository_format
1007
network_name = reference_format.network_name()
1008
# 2) try direct creation via RPC
1009
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1010
verb = 'BzrDir.create_repository'
1014
shared_str = 'False'
1016
response = a_bzrdir._call(verb, path, network_name, shared_str)
1017
except errors.UnknownSmartMethod:
1018
# Fallback - use vfs methods
1019
medium._remember_remote_is_before((1, 13))
1020
return self._vfs_initialize(a_bzrdir, shared)
1022
# Turn the response into a RemoteRepository object.
1023
format = response_tuple_to_repo_format(response[1:])
1024
# Used to support creating a real format instance when needed.
1025
format._creating_bzrdir = a_bzrdir
1026
remote_repo = RemoteRepository(a_bzrdir, format)
1027
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)
1030
199
def open(self, a_bzrdir):
1031
if not isinstance(a_bzrdir, RemoteBzrDir):
1032
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
200
assert isinstance(a_bzrdir, RemoteBzrDir)
1033
201
return a_bzrdir.open_repository()
1035
def _ensure_real(self):
1036
if self._custom_format is None:
1038
self._custom_format = _mod_repository.network_format_registry.get(
1041
raise errors.UnknownFormatError(kind='repository',
1042
format=self._network_name)
1045
def _fetch_order(self):
1047
return self._custom_format._fetch_order
1050
def _fetch_uses_deltas(self):
1052
return self._custom_format._fetch_uses_deltas
1055
def _fetch_reconcile(self):
1057
return self._custom_format._fetch_reconcile
1059
203
def get_format_description(self):
1061
return 'Remote: ' + self._custom_format.get_format_description()
204
return 'bzr remote repository'
1063
206
def __eq__(self, other):
1064
return self.__class__ is other.__class__
1066
def network_name(self):
1067
if self._network_name:
1068
return self._network_name
1069
self._creating_repo._ensure_real()
1070
return self._creating_repo._real_repository._format.network_name()
1073
def pack_compresses(self):
1075
return self._custom_format.pack_compresses
1078
def _serializer(self):
1080
return self._custom_format._serializer
1083
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1084
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):
1085
220
"""Repository accessed over rpc.
1087
222
For the moment most operations are performed using local transport-backed
1105
240
self._real_repository = None
1106
241
self.bzrdir = remote_bzrdir
1107
242
if _client is None:
1108
self._client = remote_bzrdir._client
243
self._client = client._SmartClient(self.bzrdir._medium)
1110
245
self._client = _client
1111
246
self._format = format
1112
247
self._lock_mode = None
1113
248
self._lock_token = None
1114
self._write_group_tokens = None
1115
249
self._lock_count = 0
1116
250
self._leave_lock = False
1117
# Cache of revision parents; misses are cached during read locks, and
1118
# write locks when no _real_repository has been set.
1119
self._unstacked_provider = graph.CachingParentsProvider(
1120
get_parent_map=self._get_parent_map_rpc)
1121
self._unstacked_provider.disable_cache()
1123
# These depend on the actual remote format, so force them off for
1124
# maximum compatibility. XXX: In future these should depend on the
1125
# remote repository instance, but this is irrelevant until we perform
1126
# reconcile via an RPC call.
1127
self._reconcile_does_inventory_gc = False
1128
self._reconcile_fixes_text_parents = False
1129
self._reconcile_backsup_inventory = False
1130
self.base = self.bzrdir.transport.base
1131
# Additional places to query for data.
1132
self._fallback_repositories = []
1135
def user_transport(self):
1136
return self.bzrdir.user_transport
1139
def control_transport(self):
1140
# XXX: Normally you shouldn't directly get at the remote repository
1141
# transport, but I'm not sure it's worth making this method
1142
# optional -- mbp 2010-04-21
1143
return self.bzrdir.get_repository_transport(None)
1146
return "%s(%s)" % (self.__class__.__name__, self.base)
1150
def abort_write_group(self, suppress_errors=False):
1151
"""Complete a write group on the decorated repository.
1153
Smart methods perform operations in a single step so this API
1154
is not really applicable except as a compatibility thunk
1155
for older plugins that don't use e.g. the CommitBuilder
1158
:param suppress_errors: see Repository.abort_write_group.
1160
if self._real_repository:
1162
return self._real_repository.abort_write_group(
1163
suppress_errors=suppress_errors)
1164
if not self.is_in_write_group():
1166
mutter('(suppressed) not in write group')
1168
raise errors.BzrError("not in write group")
1169
path = self.bzrdir._path_for_remote_call(self._client)
1171
response = self._call('Repository.abort_write_group', path,
1172
self._lock_token, self._write_group_tokens)
1173
except Exception, exc:
1174
self._write_group = None
1175
if not suppress_errors:
1177
mutter('abort_write_group failed')
1178
log_exception_quietly()
1179
note(gettext('bzr: ERROR (ignored): %s'), exc)
1181
if response != ('ok', ):
1182
raise errors.UnexpectedSmartServerResponse(response)
1183
self._write_group_tokens = None
1186
def chk_bytes(self):
1187
"""Decorate the real repository for now.
1189
In the long term a full blown network facility is needed to avoid
1190
creating a real repository object locally.
1193
return self._real_repository.chk_bytes
1195
def commit_write_group(self):
1196
"""Complete a write group on the decorated repository.
1198
Smart methods perform operations in a single step so this API
1199
is not really applicable except as a compatibility thunk
1200
for older plugins that don't use e.g. the CommitBuilder
1203
if self._real_repository:
1205
return self._real_repository.commit_write_group()
1206
if not self.is_in_write_group():
1207
raise errors.BzrError("not in write group")
1208
path = self.bzrdir._path_for_remote_call(self._client)
1209
response = self._call('Repository.commit_write_group', path,
1210
self._lock_token, self._write_group_tokens)
1211
if response != ('ok', ):
1212
raise errors.UnexpectedSmartServerResponse(response)
1213
self._write_group_tokens = None
1215
def resume_write_group(self, tokens):
1216
if self._real_repository:
1217
return self._real_repository.resume_write_group(tokens)
1218
path = self.bzrdir._path_for_remote_call(self._client)
1220
response = self._call('Repository.check_write_group', path,
1221
self._lock_token, tokens)
1222
except errors.UnknownSmartMethod:
1224
return self._real_repository.resume_write_group(tokens)
1225
if response != ('ok', ):
1226
raise errors.UnexpectedSmartServerResponse(response)
1227
self._write_group_tokens = tokens
1229
def suspend_write_group(self):
1230
if self._real_repository:
1231
return self._real_repository.suspend_write_group()
1232
ret = self._write_group_tokens or []
1233
self._write_group_tokens = None
1236
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1238
return self._real_repository.get_missing_parent_inventories(
1239
check_for_missing_texts=check_for_missing_texts)
1241
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1243
return self._real_repository.get_rev_id_for_revno(
1246
def get_rev_id_for_revno(self, revno, known_pair):
1247
"""See Repository.get_rev_id_for_revno."""
1248
path = self.bzrdir._path_for_remote_call(self._client)
1250
if self._client._medium._is_remote_before((1, 17)):
1251
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1252
response = self._call(
1253
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1254
except errors.UnknownSmartMethod:
1255
self._client._medium._remember_remote_is_before((1, 17))
1256
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1257
if response[0] == 'ok':
1258
return True, response[1]
1259
elif response[0] == 'history-incomplete':
1260
known_pair = response[1:3]
1261
for fallback in self._fallback_repositories:
1262
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1267
# Not found in any fallbacks
1268
return False, known_pair
1270
raise errors.UnexpectedSmartServerResponse(response)
1272
252
def _ensure_real(self):
1273
253
"""Ensure that there is a _real_repository set.
1275
255
Used before calls to self._real_repository.
1277
Note that _ensure_real causes many roundtrips to the server which are
1278
not desirable, and prevents the use of smart one-roundtrip RPC's to
1279
perform complex operations (such as accessing parent data, streaming
1280
revisions etc). Adding calls to _ensure_real should only be done when
1281
bringing up new functionality, adding fallbacks for smart methods that
1282
require a fallback path, and never to replace an existing smart method
1283
invocation. If in doubt chat to the bzr network team.
1285
if self._real_repository is None:
1286
if 'hpssvfs' in debug.debug_flags:
1288
warning('VFS Repository access triggered\n%s',
1289
''.join(traceback.format_stack()))
1290
self._unstacked_provider.missing_keys.clear()
257
if not self._real_repository:
1291
258
self.bzrdir._ensure_real()
1292
self._set_real_repository(
1293
self.bzrdir._real_bzrdir.open_repository())
1295
def _translate_error(self, err, **context):
1296
self.bzrdir._translate_error(err, repository=self, **context)
1298
def find_text_key_references(self):
1299
"""Find the text key references within the repository.
1301
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1302
to whether they were referred to by the inventory of the
1303
revision_id that they contain. The inventory texts from all present
1304
revision ids are assessed to generate this report.
1307
return self._real_repository.find_text_key_references()
1309
def _generate_text_key_index(self):
1310
"""Generate a new text key index for the repository.
1312
This is an expensive function that will take considerable time to run.
1314
:return: A dict mapping (file_id, revision_id) tuples to a list of
1315
parents, also (file_id, revision_id) tuples.
1318
return self._real_repository._generate_text_key_index()
1320
def _get_revision_graph(self, revision_id):
1321
"""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()."""
1322
264
if revision_id is None:
1323
265
revision_id = ''
1324
elif _mod_revision.is_null(revision_id):
266
elif revision_id == NULL_REVISION:
1327
269
path = self.bzrdir._path_for_remote_call(self._client)
1328
response = self._call_expecting_body(
270
assert type(revision_id) is str
271
response = self._client.call_expecting_body(
1329
272
'Repository.get_revision_graph', path, revision_id)
1330
response_tuple, response_handler = response
1331
if response_tuple[0] != 'ok':
1332
raise errors.UnexpectedSmartServerResponse(response_tuple)
1333
coded = response_handler.read_body_bytes()
1335
# no revisions in this repository!
1337
lines = coded.split('\n')
1340
d = tuple(line.split())
1341
revision_graph[d[0]] = d[1:]
1343
return revision_graph
1345
def _get_sink(self):
1346
"""See Repository._get_sink()."""
1347
return RemoteStreamSink(self)
1349
def _get_source(self, to_format):
1350
"""Return a source for streaming from this repository."""
1351
return RemoteStreamSource(self, to_format)
1354
def get_file_graph(self):
1355
return graph.Graph(self.texts)
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)
1358
292
def has_revision(self, revision_id):
1359
"""True if this repository has a copy of the revision."""
1360
# Copy of bzrlib.repository.Repository.has_revision
1361
return revision_id in self.has_revisions((revision_id,))
1364
def has_revisions(self, revision_ids):
1365
"""Probe to find out the presence of multiple revisions.
1367
:param revision_ids: An iterable of revision_ids.
1368
:return: A set of the revision_ids that were present.
1370
# Copy of bzrlib.repository.Repository.has_revisions
1371
parent_map = self.get_parent_map(revision_ids)
1372
result = set(parent_map)
1373
if _mod_revision.NULL_REVISION in revision_ids:
1374
result.add(_mod_revision.NULL_REVISION)
1377
def _has_same_fallbacks(self, other_repo):
1378
"""Returns true if the repositories have the same fallbacks."""
1379
# XXX: copied from Repository; it should be unified into a base class
1380
# <https://bugs.launchpad.net/bzr/+bug/401622>
1381
my_fb = self._fallback_repositories
1382
other_fb = other_repo._fallback_repositories
1383
if len(my_fb) != len(other_fb):
1385
for f, g in zip(my_fb, other_fb):
1386
if not f.has_same_location(g):
1390
def has_same_location(self, other):
1391
# TODO: Move to RepositoryBase and unify with the regular Repository
1392
# one; unfortunately the tests rely on slightly different behaviour at
1393
# present -- mbp 20090710
1394
return (self.__class__ is other.__class__ and
1395
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'
1397
302
def get_graph(self, other_repository=None):
1398
303
"""Return the graph for this repository format"""
1399
parents_provider = self._make_parents_provider(other_repository)
1400
return graph.Graph(parents_provider)
1403
def get_known_graph_ancestry(self, revision_ids):
1404
"""Return the known graph for a set of revision ids and their ancestors.
1406
st = static_tuple.StaticTuple
1407
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1408
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1409
return graph.GraphThunkIdsToKeys(known_graph)
304
return self._real_repository.get_graph(other_repository)
1411
306
def gather_stats(self, revid=None, committers=None):
1412
307
"""See Repository.gather_stats()."""
1413
308
path = self.bzrdir._path_for_remote_call(self._client)
1414
# revid can be None to indicate no revisions, not just NULL_REVISION
1415
if revid is None or _mod_revision.is_null(revid):
309
if revid in (None, NULL_REVISION):
1418
312
fmt_revid = revid
1964
520
@needs_read_lock
1965
521
def clone(self, a_bzrdir, revision_id=None):
1966
dest_repo = self._create_sprouting_repo(
1967
a_bzrdir, shared=self.is_shared())
1968
self.copy_content_into(dest_repo, revision_id)
523
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1971
525
def make_working_trees(self):
1972
"""See Repository.make_working_trees"""
1973
path = self.bzrdir._path_for_remote_call(self._client)
1975
response = self._call('Repository.make_working_trees', path)
1976
except errors.UnknownSmartMethod:
1978
return self._real_repository.make_working_trees()
1979
if response[0] not in ('yes', 'no'):
1980
raise SmartProtocolError('unexpected response code %s' % (response,))
1981
return response[0] == 'yes'
1983
def refresh_data(self):
1984
"""Re-read any data needed to synchronise with disk.
1986
This method is intended to be called after another repository instance
1987
(such as one used by a smart server) has inserted data into the
1988
repository. On all repositories this will work outside of write groups.
1989
Some repository formats (pack and newer for bzrlib native formats)
1990
support refresh_data inside write groups. If called inside a write
1991
group on a repository that does not support refreshing in a write group
1992
IsInWriteGroupError will be raised.
1994
if self._real_repository is not None:
1995
self._real_repository.refresh_data()
1997
def revision_ids_to_search_result(self, result_set):
1998
"""Convert a set of revision ids to a graph SearchResult."""
1999
result_parents = set()
2000
for parents in self.get_graph().get_parent_map(
2001
result_set).itervalues():
2002
result_parents.update(parents)
2003
included_keys = result_set.intersection(result_parents)
2004
start_keys = result_set.difference(included_keys)
2005
exclude_keys = result_parents.difference(result_set)
2006
result = vf_search.SearchResult(start_keys, exclude_keys,
2007
len(result_set), result_set)
2011
def search_missing_revision_ids(self, other,
2012
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
2013
find_ghosts=True, revision_ids=None, if_present_ids=None,
2015
"""Return the revision ids that other has that this does not.
2017
These are returned in topological order.
2019
revision_id: only return revision ids included by revision_id.
2021
if symbol_versioning.deprecated_passed(revision_id):
2022
symbol_versioning.warn(
2023
'search_missing_revision_ids(revision_id=...) was '
2024
'deprecated in 2.4. Use revision_ids=[...] instead.',
2025
DeprecationWarning, stacklevel=2)
2026
if revision_ids is not None:
2027
raise AssertionError(
2028
'revision_ids is mutually exclusive with revision_id')
2029
if revision_id is not None:
2030
revision_ids = [revision_id]
2031
inter_repo = _mod_repository.InterRepository.get(other, self)
2032
return inter_repo.search_missing_revision_ids(
2033
find_ghosts=find_ghosts, revision_ids=revision_ids,
2034
if_present_ids=if_present_ids, limit=limit)
2036
def fetch(self, source, revision_id=None, find_ghosts=False,
2038
# No base implementation to use as RemoteRepository is not a subclass
2039
# of Repository; so this is a copy of Repository.fetch().
2040
if fetch_spec is not None and revision_id is not None:
2041
raise AssertionError(
2042
"fetch_spec and revision_id are mutually exclusive.")
2043
if self.is_in_write_group():
2044
raise errors.InternalBzrError(
2045
"May not fetch while in a write group.")
2046
# fast path same-url fetch operations
2047
if (self.has_same_location(source)
2048
and fetch_spec is None
2049
and self._has_same_fallbacks(source)):
2050
# check that last_revision is in 'from' and then return a
2052
if (revision_id is not None and
2053
not _mod_revision.is_null(revision_id)):
2054
self.get_revision(revision_id)
2056
# if there is no specific appropriate InterRepository, this will get
2057
# the InterRepository base class, which raises an
2058
# IncompatibleRepositories when asked to fetch.
2059
inter = _mod_repository.InterRepository.get(source, self)
2060
if (fetch_spec is not None and
2061
not getattr(inter, "supports_fetch_spec", False)):
2062
raise errors.UnsupportedOperation(
2063
"fetch_spec not supported for %r" % inter)
2064
return inter.fetch(revision_id=revision_id,
2065
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
526
"""RemoteRepositories never create working trees by default."""
529
def fetch(self, source, revision_id=None, pb=None):
531
return self._real_repository.fetch(
532
source, revision_id=revision_id, pb=pb)
2067
534
def create_bundle(self, target, base, fileobj, format=None):
2068
535
self._ensure_real()
2069
536
self._real_repository.create_bundle(target, base, fileobj, format)
539
def control_weaves(self):
541
return self._real_repository.control_weaves
2071
543
@needs_read_lock
2072
@symbol_versioning.deprecated_method(
2073
symbol_versioning.deprecated_in((2, 4, 0)))
2074
544
def get_ancestry(self, revision_id, topo_sorted=True):
2075
545
self._ensure_real()
2076
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()
2078
553
def fileids_altered_by_revision_ids(self, revision_ids):
2079
554
self._ensure_real()
2080
555
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2082
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
2084
return self._real_repository._get_versioned_file_checker(
2085
revisions, revision_versions_cache)
2087
def _iter_files_bytes_rpc(self, desired_files, absent):
2088
path = self.bzrdir._path_for_remote_call(self._client)
2091
for (file_id, revid, identifier) in desired_files:
2092
lines.append("%s\0%s" % (
2093
osutils.safe_file_id(file_id),
2094
osutils.safe_revision_id(revid)))
2095
identifiers.append(identifier)
2096
(response_tuple, response_handler) = (
2097
self._call_with_body_bytes_expecting_body(
2098
"Repository.iter_files_bytes", (path, ), "\n".join(lines)))
2099
if response_tuple != ('ok', ):
2100
response_handler.cancel_read_body()
2101
raise errors.UnexpectedSmartServerResponse(response_tuple)
2102
byte_stream = response_handler.read_streamed_body()
2103
def decompress_stream(start, byte_stream, unused):
2104
decompressor = zlib.decompressobj()
2105
yield decompressor.decompress(start)
2106
while decompressor.unused_data == "":
2108
data = byte_stream.next()
2109
except StopIteration:
2111
yield decompressor.decompress(data)
2112
yield decompressor.flush()
2113
unused.append(decompressor.unused_data)
2116
while not "\n" in unused:
2117
unused += byte_stream.next()
2118
header, rest = unused.split("\n", 1)
2119
args = header.split("\0")
2120
if args[0] == "absent":
2121
absent[identifiers[int(args[3])]] = (args[1], args[2])
2124
elif args[0] == "ok":
2127
raise errors.UnexpectedSmartServerResponse(args)
2129
yield (identifiers[idx],
2130
decompress_stream(rest, byte_stream, unused_chunks))
2131
unused = "".join(unused_chunks)
2133
def iter_files_bytes(self, desired_files):
2134
"""See Repository.iter_file_bytes.
2138
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2139
desired_files, absent):
2140
yield identifier, bytes_iterator
2141
for fallback in self._fallback_repositories:
2144
desired_files = [(key[0], key[1], identifier) for
2145
(identifier, key) in absent.iteritems()]
2146
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2147
del absent[identifier]
2148
yield identifier, bytes_iterator
2150
# There may be more missing items, but raise an exception
2152
missing_identifier = absent.keys()[0]
2153
missing_key = absent[missing_identifier]
2154
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2155
file_id=missing_key[0])
2156
except errors.UnknownSmartMethod:
2158
for (identifier, bytes_iterator) in (
2159
self._real_repository.iter_files_bytes(desired_files)):
2160
yield identifier, bytes_iterator
2162
def get_cached_parent_map(self, revision_ids):
2163
"""See bzrlib.CachingParentsProvider.get_cached_parent_map"""
2164
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2166
def get_parent_map(self, revision_ids):
2167
"""See bzrlib.Graph.get_parent_map()."""
2168
return self._make_parents_provider().get_parent_map(revision_ids)
2170
def _get_parent_map_rpc(self, keys):
2171
"""Helper for get_parent_map that performs the RPC."""
2172
medium = self._client._medium
2173
if medium._is_remote_before((1, 2)):
2174
# We already found out that the server can't understand
2175
# Repository.get_parent_map requests, so just fetch the whole
2178
# Note that this reads the whole graph, when only some keys are
2179
# wanted. On this old server there's no way (?) to get them all
2180
# in one go, and the user probably will have seen a warning about
2181
# the server being old anyhow.
2182
rg = self._get_revision_graph(None)
2183
# There is an API discrepancy between get_parent_map and
2184
# get_revision_graph. Specifically, a "key:()" pair in
2185
# get_revision_graph just means a node has no parents. For
2186
# "get_parent_map" it means the node is a ghost. So fix up the
2187
# graph to correct this.
2188
# https://bugs.launchpad.net/bzr/+bug/214894
2189
# There is one other "bug" which is that ghosts in
2190
# get_revision_graph() are not returned at all. But we won't worry
2191
# about that for now.
2192
for node_id, parent_ids in rg.iteritems():
2193
if parent_ids == ():
2194
rg[node_id] = (NULL_REVISION,)
2195
rg[NULL_REVISION] = ()
2200
raise ValueError('get_parent_map(None) is not valid')
2201
if NULL_REVISION in keys:
2202
keys.discard(NULL_REVISION)
2203
found_parents = {NULL_REVISION:()}
2205
return found_parents
2208
# TODO(Needs analysis): We could assume that the keys being requested
2209
# from get_parent_map are in a breadth first search, so typically they
2210
# will all be depth N from some common parent, and we don't have to
2211
# have the server iterate from the root parent, but rather from the
2212
# keys we're searching; and just tell the server the keyspace we
2213
# already have; but this may be more traffic again.
2215
# Transform self._parents_map into a search request recipe.
2216
# TODO: Manage this incrementally to avoid covering the same path
2217
# repeatedly. (The server will have to on each request, but the less
2218
# work done the better).
2220
# Negative caching notes:
2221
# new server sends missing when a request including the revid
2222
# 'include-missing:' is present in the request.
2223
# missing keys are serialised as missing:X, and we then call
2224
# provider.note_missing(X) for-all X
2225
parents_map = self._unstacked_provider.get_cached_map()
2226
if parents_map is None:
2227
# Repository is not locked, so there's no cache.
2229
if _DEFAULT_SEARCH_DEPTH <= 0:
2230
(start_set, stop_keys,
2231
key_count) = vf_search.search_result_from_parent_map(
2232
parents_map, self._unstacked_provider.missing_keys)
2234
(start_set, stop_keys,
2235
key_count) = vf_search.limited_search_result_from_parent_map(
2236
parents_map, self._unstacked_provider.missing_keys,
2237
keys, depth=_DEFAULT_SEARCH_DEPTH)
2238
recipe = ('manual', start_set, stop_keys, key_count)
2239
body = self._serialise_search_recipe(recipe)
2240
path = self.bzrdir._path_for_remote_call(self._client)
2242
if type(key) is not str:
2244
"key %r not a plain string" % (key,))
2245
verb = 'Repository.get_parent_map'
2246
args = (path, 'include-missing:') + tuple(keys)
2248
response = self._call_with_body_bytes_expecting_body(
2250
except errors.UnknownSmartMethod:
2251
# Server does not support this method, so get the whole graph.
2252
# Worse, we have to force a disconnection, because the server now
2253
# doesn't realise it has a body on the wire to consume, so the
2254
# only way to recover is to abandon the connection.
2256
'Server is too old for fast get_parent_map, reconnecting. '
2257
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2259
# To avoid having to disconnect repeatedly, we keep track of the
2260
# fact the server doesn't understand remote methods added in 1.2.
2261
medium._remember_remote_is_before((1, 2))
2262
# Recurse just once and we should use the fallback code.
2263
return self._get_parent_map_rpc(keys)
2264
response_tuple, response_handler = response
2265
if response_tuple[0] not in ['ok']:
2266
response_handler.cancel_read_body()
2267
raise errors.UnexpectedSmartServerResponse(response_tuple)
2268
if response_tuple[0] == 'ok':
2269
coded = bz2.decompress(response_handler.read_body_bytes())
2271
# no revisions found
2273
lines = coded.split('\n')
2276
d = tuple(line.split())
2278
revision_graph[d[0]] = d[1:]
2281
if d[0].startswith('missing:'):
2283
self._unstacked_provider.note_missing_key(revid)
2285
# no parents - so give the Graph result
2287
revision_graph[d[0]] = (NULL_REVISION,)
2288
return revision_graph
2290
557
@needs_read_lock
2291
558
def get_signature_text(self, revision_id):
2292
path = self.bzrdir._path_for_remote_call(self._client)
2294
response_tuple, response_handler = self._call_expecting_body(
2295
'Repository.get_revision_signature_text', path, revision_id)
2296
except errors.UnknownSmartMethod:
2298
return self._real_repository.get_signature_text(revision_id)
2299
except errors.NoSuchRevision, err:
2300
for fallback in self._fallback_repositories:
2302
return fallback.get_signature_text(revision_id)
2303
except errors.NoSuchRevision:
2307
if response_tuple[0] != 'ok':
2308
raise errors.UnexpectedSmartServerResponse(response_tuple)
2309
return response_handler.read_body_bytes()
2312
def _get_inventory_xml(self, revision_id):
2313
# This call is used by older working tree formats,
2314
# which stored a serialized basis inventory.
2316
return self._real_repository._get_inventory_xml(revision_id)
560
return self._real_repository.get_signature_text(revision_id)
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)
2319
577
def reconcile(self, other=None, thorough=False):
2320
from bzrlib.reconcile import RepoReconciler
2321
path = self.bzrdir._path_for_remote_call(self._client)
2323
response, handler = self._call_expecting_body(
2324
'Repository.reconcile', path, self._lock_token)
2325
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2327
return self._real_repository.reconcile(other=other, thorough=thorough)
2328
if response != ('ok', ):
2329
raise errors.UnexpectedSmartServerResponse(response)
2330
body = handler.read_body_bytes()
2331
result = RepoReconciler(self)
2332
for line in body.split('\n'):
2335
key, val_text = line.split(':')
2336
if key == "garbage_inventories":
2337
result.garbage_inventories = int(val_text)
2338
elif key == "inconsistent_parents":
2339
result.inconsistent_parents = int(val_text)
2341
mutter("unknown reconcile key %r" % key)
579
return self._real_repository.reconcile(other=other, thorough=thorough)
2344
581
def all_revision_ids(self):
2345
path = self.bzrdir._path_for_remote_call(self._client)
2347
response_tuple, response_handler = self._call_expecting_body(
2348
"Repository.all_revision_ids", path)
2349
except errors.UnknownSmartMethod:
2351
return self._real_repository.all_revision_ids()
2352
if response_tuple != ("ok", ):
2353
raise errors.UnexpectedSmartServerResponse(response_tuple)
2354
revids = set(response_handler.read_body_bytes().splitlines())
2355
for fallback in self._fallback_repositories:
2356
revids.update(set(fallback.all_revision_ids()))
2359
def _filtered_revision_trees(self, revision_ids, file_ids):
2360
"""Return Tree for a revision on this branch with only some files.
2362
:param revision_ids: a sequence of revision-ids;
2363
a revision-id may not be None or 'null:'
2364
:param file_ids: if not None, the result is filtered
2365
so that only those file-ids, their parents and their
2366
children are included.
2368
inventories = self.iter_inventories(revision_ids)
2369
for inv in inventories:
2370
# Should we introduce a FilteredRevisionTree class rather
2371
# than pre-filter the inventory here?
2372
filtered_inv = inv.filter(file_ids)
2373
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2376
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2377
medium = self._client._medium
2378
if medium._is_remote_before((1, 2)):
2380
for delta in self._real_repository.get_deltas_for_revisions(
2381
revisions, specific_fileids):
2384
# Get the revision-ids of interest
2385
required_trees = set()
2386
for revision in revisions:
2387
required_trees.add(revision.revision_id)
2388
required_trees.update(revision.parent_ids[:1])
2390
# Get the matching filtered trees. Note that it's more
2391
# efficient to pass filtered trees to changes_from() rather
2392
# than doing the filtering afterwards. changes_from() could
2393
# arguably do the filtering itself but it's path-based, not
2394
# file-id based, so filtering before or afterwards is
2396
if specific_fileids is None:
2397
trees = dict((t.get_revision_id(), t) for
2398
t in self.revision_trees(required_trees))
2400
trees = dict((t.get_revision_id(), t) for
2401
t in self._filtered_revision_trees(required_trees,
2404
# Calculate the deltas
2405
for revision in revisions:
2406
if not revision.parent_ids:
2407
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2409
old_tree = trees[revision.parent_ids[0]]
2410
yield trees[revision.revision_id].changes_from(old_tree)
2413
def get_revision_delta(self, revision_id, specific_fileids=None):
2414
r = self.get_revision(revision_id)
2415
return list(self.get_deltas_for_revisions([r],
2416
specific_fileids=specific_fileids))[0]
583
return self._real_repository.all_revision_ids()
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)
2418
595
@needs_read_lock
2419
596
def revision_trees(self, revision_ids):
2420
inventories = self.iter_inventories(revision_ids)
2421
for inv in inventories:
2422
yield InventoryRevisionTree(self, inv, inv.revision_id)
598
return self._real_repository.revision_trees(revision_ids)
2424
600
@needs_read_lock
2425
601
def get_revision_reconcile(self, revision_id):
2427
603
return self._real_repository.get_revision_reconcile(revision_id)
2429
605
@needs_read_lock
2430
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
606
def check(self, revision_ids):
2431
607
self._ensure_real()
2432
return self._real_repository.check(revision_ids=revision_ids,
2433
callback_refs=callback_refs, check_repo=check_repo)
608
return self._real_repository.check(revision_ids)
2435
610
def copy_content_into(self, destination, revision_id=None):
2436
"""Make a complete copy of the content in self into destination.
2438
This is a destructive operation! Do not use it on existing
2441
interrepo = _mod_repository.InterRepository.get(self, destination)
2442
return interrepo.copy_content(revision_id)
2444
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
612
return self._real_repository.copy_content_into(
613
destination, revision_id=revision_id)
615
def _copy_repository_tarball(self, destination, revision_id=None):
2445
616
# get a tarball of the remote repository, and copy from that into the
618
from bzrlib import osutils
621
from StringIO import StringIO
2448
622
# TODO: Maybe a progress bar while streaming the tarball?
2449
note(gettext("Copying repository content as tarball..."))
623
note("Copying repository content as tarball...")
2450
624
tar_file = self._get_tarball('bz2')
2451
if tar_file is None:
2453
destination = to_bzrdir.create_repository()
2455
626
tar = tarfile.open('repository', fileobj=tar_file,
2457
tmpdir = osutils.mkdtemp()
628
tmpdir = tempfile.mkdtemp()
2459
630
_extract_tar(tar, tmpdir)
2460
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
631
tmp_bzrdir = BzrDir.open(tmpdir)
2461
632
tmp_repo = tmp_bzrdir.open_repository()
2462
633
tmp_repo.copy_content_into(destination, revision_id)
2464
635
osutils.rmtree(tmpdir)
2466
637
tar_file.close()
638
# TODO: if the server doesn't support this operation, maybe do it the
639
# slow way using the _real_repository?
2468
641
# TODO: Suggestion from john: using external tar is much faster than
2469
642
# python's tarfile library, but it may not work on windows.
2472
def inventories(self):
2473
"""Decorate the real repository for now.
2475
In the long term a full blown network facility is needed to
2476
avoid creating a real repository object locally.
2479
return self._real_repository.inventories
2481
644
@needs_write_lock
2482
def pack(self, hint=None, clean_obsolete_packs=False):
2483
646
"""Compress the data within the repository.
2488
body = "".join([l+"\n" for l in hint])
2489
path = self.bzrdir._path_for_remote_call(self._client)
2491
response, handler = self._call_with_body_bytes_expecting_body(
2492
'Repository.pack', (path, self._lock_token,
2493
str(clean_obsolete_packs)), body)
2494
except errors.UnknownSmartMethod:
2496
return self._real_repository.pack(hint=hint,
2497
clean_obsolete_packs=clean_obsolete_packs)
2498
handler.cancel_read_body()
2499
if response != ('ok', ):
2500
raise errors.UnexpectedSmartServerResponse(response)
2503
def revisions(self):
2504
"""Decorate the real repository for now.
2506
In the long term a full blown network facility is needed.
648
This is not currently implemented within the smart server.
2508
650
self._ensure_real()
2509
return self._real_repository.revisions
651
return self._real_repository.pack()
2511
653
def set_make_working_trees(self, new_value):
2513
new_value_str = "True"
2515
new_value_str = "False"
2516
path = self.bzrdir._path_for_remote_call(self._client)
2518
response = self._call(
2519
'Repository.set_make_working_trees', path, new_value_str)
2520
except errors.UnknownSmartMethod:
2522
self._real_repository.set_make_working_trees(new_value)
2524
if response[0] != 'ok':
2525
raise errors.UnexpectedSmartServerResponse(response)
2528
def signatures(self):
2529
"""Decorate the real repository for now.
2531
In the long term a full blown network facility is needed to avoid
2532
creating a real repository object locally.
2535
return self._real_repository.signatures
654
raise NotImplementedError(self.set_make_working_trees)
2537
656
@needs_write_lock
2538
657
def sign_revision(self, revision_id, gpg_strategy):
2539
testament = _mod_testament.Testament.from_revision(self, revision_id)
2540
plaintext = testament.as_short_text()
2541
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2545
"""Decorate the real repository for now.
2547
In the long term a full blown network facility is needed to avoid
2548
creating a real repository object locally.
2550
658
self._ensure_real()
2551
return self._real_repository.texts
2553
def _iter_revisions_rpc(self, revision_ids):
2554
body = "\n".join(revision_ids)
2555
path = self.bzrdir._path_for_remote_call(self._client)
2556
response_tuple, response_handler = (
2557
self._call_with_body_bytes_expecting_body(
2558
"Repository.iter_revisions", (path, ), body))
2559
if response_tuple[0] != "ok":
2560
raise errors.UnexpectedSmartServerResponse(response_tuple)
2561
serializer_format = response_tuple[1]
2562
serializer = serializer_format_registry.get(serializer_format)
2563
byte_stream = response_handler.read_streamed_body()
2564
decompressor = zlib.decompressobj()
2566
for bytes in byte_stream:
2567
chunks.append(decompressor.decompress(bytes))
2568
if decompressor.unused_data != "":
2569
chunks.append(decompressor.flush())
2570
yield serializer.read_revision_from_string("".join(chunks))
2571
unused = decompressor.unused_data
2572
decompressor = zlib.decompressobj()
2573
chunks = [decompressor.decompress(unused)]
2574
chunks.append(decompressor.flush())
2575
text = "".join(chunks)
2577
yield serializer.read_revision_from_string("".join(chunks))
659
return self._real_repository.sign_revision(revision_id, gpg_strategy)
2579
661
@needs_read_lock
2580
662
def get_revisions(self, revision_ids):
2581
if revision_ids is None:
2582
revision_ids = self.all_revision_ids()
2584
for rev_id in revision_ids:
2585
if not rev_id or not isinstance(rev_id, basestring):
2586
raise errors.InvalidRevisionId(
2587
revision_id=rev_id, branch=self)
2589
missing = set(revision_ids)
2591
for rev in self._iter_revisions_rpc(revision_ids):
2592
missing.remove(rev.revision_id)
2593
revs[rev.revision_id] = rev
2594
except errors.UnknownSmartMethod:
2596
return self._real_repository.get_revisions(revision_ids)
2597
for fallback in self._fallback_repositories:
2600
for revid in list(missing):
2601
# XXX JRV 2011-11-20: It would be nice if there was a
2602
# public method on Repository that could be used to query
2603
# for revision objects *without* failing completely if one
2604
# was missing. There is VersionedFileRepository._iter_revisions,
2605
# but unfortunately that's private and not provided by
2606
# all repository implementations.
2608
revs[revid] = fallback.get_revision(revid)
2609
except errors.NoSuchRevision:
2612
missing.remove(revid)
2614
raise errors.NoSuchRevision(self, list(missing)[0])
2615
return [revs[revid] for revid in revision_ids]
664
return self._real_repository.get_revisions(revision_ids)
2617
666
def supports_rich_root(self):
2618
return self._format.rich_root_data
668
return self._real_repository.supports_rich_root()
2620
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2621
670
def iter_reverse_revision_history(self, revision_id):
2622
671
self._ensure_real()
2623
672
return self._real_repository.iter_reverse_revision_history(revision_id)
2626
675
def _serializer(self):
2627
return self._format._serializer
677
return self._real_repository._serializer
2630
679
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
2631
signature = gpg_strategy.sign(plaintext)
2632
self.add_signature_text(revision_id, signature)
2634
def add_signature_text(self, revision_id, signature):
2635
if self._real_repository:
2636
# If there is a real repository the write group will
2637
# be in the real repository as well, so use that:
2639
return self._real_repository.add_signature_text(
2640
revision_id, signature)
2641
path = self.bzrdir._path_for_remote_call(self._client)
2642
response, handler = self._call_with_body_bytes_expecting_body(
2643
'Repository.add_signature_text', (path, self._lock_token,
2644
revision_id) + tuple(self._write_group_tokens), signature)
2645
handler.cancel_read_body()
2647
if response[0] != 'ok':
2648
raise errors.UnexpectedSmartServerResponse(response)
2649
self._write_group_tokens = response[1:]
681
return self._real_repository.store_revision_signature(
682
gpg_strategy, plaintext, revision_id)
2651
684
def has_signature_for_revision_id(self, revision_id):
2652
path = self.bzrdir._path_for_remote_call(self._client)
2654
response = self._call('Repository.has_signature_for_revision_id',
2656
except errors.UnknownSmartMethod:
2658
return self._real_repository.has_signature_for_revision_id(
2660
if response[0] not in ('yes', 'no'):
2661
raise SmartProtocolError('unexpected response code %s' % (response,))
2662
if response[0] == 'yes':
2664
for fallback in self._fallback_repositories:
2665
if fallback.has_signature_for_revision_id(revision_id):
2670
def verify_revision_signature(self, revision_id, gpg_strategy):
2671
if not self.has_signature_for_revision_id(revision_id):
2672
return gpg.SIGNATURE_NOT_SIGNED, None
2673
signature = self.get_signature_text(revision_id)
2675
testament = _mod_testament.Testament.from_revision(self, revision_id)
2676
plaintext = testament.as_short_text()
2678
return gpg_strategy.verify(signature, plaintext)
2680
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2682
return self._real_repository.item_keys_introduced_by(revision_ids,
2683
_files_pb=_files_pb)
2685
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2687
return self._real_repository._find_inconsistent_revision_parents(
2690
def _check_for_inconsistent_revision_parents(self):
2692
return self._real_repository._check_for_inconsistent_revision_parents()
2694
def _make_parents_provider(self, other=None):
2695
providers = [self._unstacked_provider]
2696
if other is not None:
2697
providers.insert(0, other)
2698
return graph.StackedParentsProvider(_LazyListJoin(
2699
providers, self._fallback_repositories))
2701
def _serialise_search_recipe(self, recipe):
2702
"""Serialise a graph search recipe.
2704
:param recipe: A search recipe (start, stop, count).
2705
:return: Serialised bytes.
2707
start_keys = ' '.join(recipe[1])
2708
stop_keys = ' '.join(recipe[2])
2709
count = str(recipe[3])
2710
return '\n'.join((start_keys, stop_keys, count))
2712
def _serialise_search_result(self, search_result):
2713
parts = search_result.get_network_struct()
2714
return '\n'.join(parts)
2717
path = self.bzrdir._path_for_remote_call(self._client)
2719
response = self._call('PackRepository.autopack', path)
2720
except errors.UnknownSmartMethod:
2722
self._real_repository._pack_collection.autopack()
2725
if response[0] != 'ok':
2726
raise errors.UnexpectedSmartServerResponse(response)
2729
class RemoteStreamSink(vf_repository.StreamSink):
2731
def _insert_real(self, stream, src_format, resume_tokens):
2732
self.target_repo._ensure_real()
2733
sink = self.target_repo._real_repository._get_sink()
2734
result = sink.insert_stream(stream, src_format, resume_tokens)
2736
self.target_repo.autopack()
2739
def insert_stream(self, stream, src_format, resume_tokens):
2740
target = self.target_repo
2741
target._unstacked_provider.missing_keys.clear()
2742
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2743
if target._lock_token:
2744
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2745
lock_args = (target._lock_token or '',)
2747
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2749
client = target._client
2750
medium = client._medium
2751
path = target.bzrdir._path_for_remote_call(client)
2752
# Probe for the verb to use with an empty stream before sending the
2753
# real stream to it. We do this both to avoid the risk of sending a
2754
# large request that is then rejected, and because we don't want to
2755
# implement a way to buffer, rewind, or restart the stream.
2757
for verb, required_version in candidate_calls:
2758
if medium._is_remote_before(required_version):
2761
# We've already done the probing (and set _is_remote_before) on
2762
# a previous insert.
2765
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2767
response = client.call_with_body_stream(
2768
(verb, path, '') + lock_args, byte_stream)
2769
except errors.UnknownSmartMethod:
2770
medium._remember_remote_is_before(required_version)
2776
return self._insert_real(stream, src_format, resume_tokens)
2777
self._last_inv_record = None
2778
self._last_substream = None
2779
if required_version < (1, 19):
2780
# Remote side doesn't support inventory deltas. Wrap the stream to
2781
# make sure we don't send any. If the stream contains inventory
2782
# deltas we'll interrupt the smart insert_stream request and
2784
stream = self._stop_stream_if_inventory_delta(stream)
2785
byte_stream = smart_repo._stream_to_byte_stream(
2787
resume_tokens = ' '.join(resume_tokens)
2788
response = client.call_with_body_stream(
2789
(verb, path, resume_tokens) + lock_args, byte_stream)
2790
if response[0][0] not in ('ok', 'missing-basis'):
2791
raise errors.UnexpectedSmartServerResponse(response)
2792
if self._last_substream is not None:
2793
# The stream included an inventory-delta record, but the remote
2794
# side isn't new enough to support them. So we need to send the
2795
# rest of the stream via VFS.
2796
self.target_repo.refresh_data()
2797
return self._resume_stream_with_vfs(response, src_format)
2798
if response[0][0] == 'missing-basis':
2799
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2800
resume_tokens = tokens
2801
return resume_tokens, set(missing_keys)
2803
self.target_repo.refresh_data()
2806
def _resume_stream_with_vfs(self, response, src_format):
2807
"""Resume sending a stream via VFS, first resending the record and
2808
substream that couldn't be sent via an insert_stream verb.
2810
if response[0][0] == 'missing-basis':
2811
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2812
# Ignore missing_keys, we haven't finished inserting yet
2815
def resume_substream():
2816
# Yield the substream that was interrupted.
2817
for record in self._last_substream:
2819
self._last_substream = None
2820
def resume_stream():
2821
# Finish sending the interrupted substream
2822
yield ('inventory-deltas', resume_substream())
2823
# Then simply continue sending the rest of the stream.
2824
for substream_kind, substream in self._last_stream:
2825
yield substream_kind, substream
2826
return self._insert_real(resume_stream(), src_format, tokens)
2828
def _stop_stream_if_inventory_delta(self, stream):
2829
"""Normally this just lets the original stream pass-through unchanged.
2831
However if any 'inventory-deltas' substream occurs it will stop
2832
streaming, and store the interrupted substream and stream in
2833
self._last_substream and self._last_stream so that the stream can be
2834
resumed by _resume_stream_with_vfs.
2837
stream_iter = iter(stream)
2838
for substream_kind, substream in stream_iter:
2839
if substream_kind == 'inventory-deltas':
2840
self._last_substream = substream
2841
self._last_stream = stream_iter
2844
yield substream_kind, substream
2847
class RemoteStreamSource(vf_repository.StreamSource):
2848
"""Stream data from a remote server."""
2850
def get_stream(self, search):
2851
if (self.from_repository._fallback_repositories and
2852
self.to_format._fetch_order == 'topological'):
2853
return self._real_stream(self.from_repository, search)
2856
repos = [self.from_repository]
2862
repos.extend(repo._fallback_repositories)
2863
sources.append(repo)
2864
return self.missing_parents_chain(search, sources)
2866
def get_stream_for_missing_keys(self, missing_keys):
2867
self.from_repository._ensure_real()
2868
real_repo = self.from_repository._real_repository
2869
real_source = real_repo._get_source(self.to_format)
2870
return real_source.get_stream_for_missing_keys(missing_keys)
2872
def _real_stream(self, repo, search):
2873
"""Get a stream for search from repo.
2875
This never called RemoteStreamSource.get_stream, and is a helper
2876
for RemoteStreamSource._get_stream to allow getting a stream
2877
reliably whether fallback back because of old servers or trying
2878
to stream from a non-RemoteRepository (which the stacked support
2881
source = repo._get_source(self.to_format)
2882
if isinstance(source, RemoteStreamSource):
2884
source = repo._real_repository._get_source(self.to_format)
2885
return source.get_stream(search)
2887
def _get_stream(self, repo, search):
2888
"""Core worker to get a stream from repo for search.
2890
This is used by both get_stream and the stacking support logic. It
2891
deliberately gets a stream for repo which does not need to be
2892
self.from_repository. In the event that repo is not Remote, or
2893
cannot do a smart stream, a fallback is made to the generic
2894
repository._get_stream() interface, via self._real_stream.
2896
In the event of stacking, streams from _get_stream will not
2897
contain all the data for search - this is normal (see get_stream).
2899
:param repo: A repository.
2900
:param search: A search.
2902
# Fallbacks may be non-smart
2903
if not isinstance(repo, RemoteRepository):
2904
return self._real_stream(repo, search)
2905
client = repo._client
2906
medium = client._medium
2907
path = repo.bzrdir._path_for_remote_call(client)
2908
search_bytes = repo._serialise_search_result(search)
2909
args = (path, self.to_format.network_name())
2911
('Repository.get_stream_1.19', (1, 19)),
2912
('Repository.get_stream', (1, 13))]
2915
for verb, version in candidate_verbs:
2916
if medium._is_remote_before(version):
2919
response = repo._call_with_body_bytes_expecting_body(
2920
verb, args, search_bytes)
2921
except errors.UnknownSmartMethod:
2922
medium._remember_remote_is_before(version)
2923
except errors.UnknownErrorFromSmartServer, e:
2924
if isinstance(search, vf_search.EverythingResult):
2925
error_verb = e.error_from_smart_server.error_verb
2926
if error_verb == 'BadSearch':
2927
# Pre-2.4 servers don't support this sort of search.
2928
# XXX: perhaps falling back to VFS on BadSearch is a
2929
# good idea in general? It might provide a little bit
2930
# of protection against client-side bugs.
2931
medium._remember_remote_is_before((2, 4))
2935
response_tuple, response_handler = response
2939
return self._real_stream(repo, search)
2940
if response_tuple[0] != 'ok':
2941
raise errors.UnexpectedSmartServerResponse(response_tuple)
2942
byte_stream = response_handler.read_streamed_body()
2943
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2944
self._record_counter)
2945
if src_format.network_name() != repo._format.network_name():
2946
raise AssertionError(
2947
"Mismatched RemoteRepository and stream src %r, %r" % (
2948
src_format.network_name(), repo._format.network_name()))
2951
def missing_parents_chain(self, search, sources):
2952
"""Chain multiple streams together to handle stacking.
2954
:param search: The overall search to satisfy with streams.
2955
:param sources: A list of Repository objects to query.
2957
self.from_serialiser = self.from_repository._format._serializer
2958
self.seen_revs = set()
2959
self.referenced_revs = set()
2960
# If there are heads in the search, or the key count is > 0, we are not
2962
while not search.is_empty() and len(sources) > 1:
2963
source = sources.pop(0)
2964
stream = self._get_stream(source, search)
2965
for kind, substream in stream:
2966
if kind != 'revisions':
2967
yield kind, substream
2969
yield kind, self.missing_parents_rev_handler(substream)
2970
search = search.refine(self.seen_revs, self.referenced_revs)
2971
self.seen_revs = set()
2972
self.referenced_revs = set()
2973
if not search.is_empty():
2974
for kind, stream in self._get_stream(sources[0], search):
2977
def missing_parents_rev_handler(self, substream):
2978
for content in substream:
2979
revision_bytes = content.get_bytes_as('fulltext')
2980
revision = self.from_serialiser.read_revision_from_string(
2982
self.seen_revs.add(content.key[-1])
2983
self.referenced_revs.update(revision.parent_ids)
686
return self._real_repository.has_signature_for_revision_id(revision_id)
2987
689
class RemoteBranchLockableFiles(LockableFiles):
2988
690
"""A 'LockableFiles' implementation that talks to a smart server.
2990
692
This is not a public interface class.
3003
705
self._dir_mode = None
3004
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)
3007
727
class RemoteBranchFormat(branch.BranchFormat):
3009
def __init__(self, network_name=None):
3010
super(RemoteBranchFormat, self).__init__()
3011
self._matchingbzrdir = RemoteBzrDirFormat()
3012
self._matchingbzrdir.set_branch_format(self)
3013
self._custom_format = None
3014
self._network_name = network_name
3016
729
def __eq__(self, other):
3017
return (isinstance(other, RemoteBranchFormat) and
730
return (isinstance(other, RemoteBranchFormat) and
3018
731
self.__dict__ == other.__dict__)
3020
def _ensure_real(self):
3021
if self._custom_format is None:
3023
self._custom_format = branch.network_format_registry.get(
3026
raise errors.UnknownFormatError(kind='branch',
3027
format=self._network_name)
3029
733
def get_format_description(self):
3031
return 'Remote: ' + self._custom_format.get_format_description()
3033
def network_name(self):
3034
return self._network_name
3036
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
3037
return a_bzrdir.open_branch(name=name,
3038
ignore_fallbacks=ignore_fallbacks)
3040
def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
3041
# Initialisation when using a local bzrdir object, or a non-vfs init
3042
# method is not available on the server.
3043
# self._custom_format is always set - the start of initialize ensures
3045
if isinstance(a_bzrdir, RemoteBzrDir):
3046
a_bzrdir._ensure_real()
3047
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
3048
name, append_revisions_only=append_revisions_only)
3050
# We assume the bzrdir is parameterised; it may not be.
3051
result = self._custom_format.initialize(a_bzrdir, name,
3052
append_revisions_only=append_revisions_only)
3053
if (isinstance(a_bzrdir, RemoteBzrDir) and
3054
not isinstance(result, RemoteBranch)):
3055
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3059
def initialize(self, a_bzrdir, name=None, repository=None,
3060
append_revisions_only=None):
3061
# 1) get the network name to use.
3062
if self._custom_format:
3063
network_name = self._custom_format.network_name()
3065
# Select the current bzrlib default and ask for that.
3066
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
3067
reference_format = reference_bzrdir_format.get_branch_format()
3068
self._custom_format = reference_format
3069
network_name = reference_format.network_name()
3070
# Being asked to create on a non RemoteBzrDir:
3071
if not isinstance(a_bzrdir, RemoteBzrDir):
3072
return self._vfs_initialize(a_bzrdir, name=name,
3073
append_revisions_only=append_revisions_only)
3074
medium = a_bzrdir._client._medium
3075
if medium._is_remote_before((1, 13)):
3076
return self._vfs_initialize(a_bzrdir, name=name,
3077
append_revisions_only=append_revisions_only)
3078
# Creating on a remote bzr dir.
3079
# 2) try direct creation via RPC
3080
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
3081
if name is not None:
3082
# XXX JRV20100304: Support creating colocated branches
3083
raise errors.NoColocatedBranchSupport(self)
3084
verb = 'BzrDir.create_branch'
3086
response = a_bzrdir._call(verb, path, network_name)
3087
except errors.UnknownSmartMethod:
3088
# Fallback - use vfs methods
3089
medium._remember_remote_is_before((1, 13))
3090
return self._vfs_initialize(a_bzrdir, name=name,
3091
append_revisions_only=append_revisions_only)
3092
if response[0] != 'ok':
3093
raise errors.UnexpectedSmartServerResponse(response)
3094
# Turn the response into a RemoteRepository object.
3095
format = RemoteBranchFormat(network_name=response[1])
3096
repo_format = response_tuple_to_repo_format(response[3:])
3097
repo_path = response[2]
3098
if repository is not None:
3099
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
3100
url_diff = urlutils.relative_url(repository.user_url,
3103
raise AssertionError(
3104
'repository.user_url %r does not match URL from server '
3105
'response (%r + %r)'
3106
% (repository.user_url, a_bzrdir.user_url, repo_path))
3107
remote_repo = repository
3110
repo_bzrdir = a_bzrdir
3112
repo_bzrdir = RemoteBzrDir(
3113
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
3115
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3116
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
3117
format=format, setup_stacking=False, name=name)
3118
if append_revisions_only:
3119
remote_branch.set_append_revisions_only(append_revisions_only)
3120
# XXX: We know this is a new branch, so it must have revno 0, revid
3121
# NULL_REVISION. Creating the branch locked would make this be unable
3122
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3123
remote_branch._last_revision_info_cache = 0, NULL_REVISION
3124
return remote_branch
3126
def make_tags(self, branch):
3128
return self._custom_format.make_tags(branch)
3130
def supports_tags(self):
3131
# Remote branches might support tags, but we won't know until we
3132
# access the real remote branch.
3134
return self._custom_format.supports_tags()
3136
def supports_stacking(self):
3138
return self._custom_format.supports_stacking()
3140
def supports_set_append_revisions_only(self):
3142
return self._custom_format.supports_set_append_revisions_only()
3144
def _use_default_local_heads_to_fetch(self):
3145
# If the branch format is a metadir format *and* its heads_to_fetch
3146
# implementation is not overridden vs the base class, we can use the
3147
# base class logic rather than use the heads_to_fetch RPC. This is
3148
# usually cheaper in terms of net round trips, as the last-revision and
3149
# tags info fetched is cached and would be fetched anyway.
3151
if isinstance(self._custom_format, branch.BranchFormatMetadir):
3152
branch_class = self._custom_format._branch_class()
3153
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
3154
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
3159
class RemoteBranchStore(config.IniFileStore):
3160
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3162
Note that this is specific to bzr-based formats.
3165
def __init__(self, branch):
3166
super(RemoteBranchStore, self).__init__()
3167
self.branch = branch
3169
self._real_store = None
3171
def lock_write(self, token=None):
3172
return self.branch.lock_write(token)
3175
return self.branch.unlock()
3179
# We need to be able to override the undecorated implementation
3180
self.save_without_locking()
3182
def save_without_locking(self):
3183
super(RemoteBranchStore, self).save()
3185
def external_url(self):
3186
return self.branch.user_url
3188
def _load_content(self):
3189
path = self.branch._remote_path()
3191
response, handler = self.branch._call_expecting_body(
3192
'Branch.get_config_file', path)
3193
except errors.UnknownSmartMethod:
3195
return self._real_store._load_content()
3196
if len(response) and response[0] != 'ok':
3197
raise errors.UnexpectedSmartServerResponse(response)
3198
return handler.read_body_bytes()
3200
def _save_content(self, content):
3201
path = self.branch._remote_path()
3203
response, handler = self.branch._call_with_body_bytes_expecting_body(
3204
'Branch.put_config_file', (path,
3205
self.branch._lock_token, self.branch._repo_lock_token),
3207
except errors.UnknownSmartMethod:
3209
return self._real_store._save_content(content)
3210
handler.cancel_read_body()
3211
if response != ('ok', ):
3212
raise errors.UnexpectedSmartServerResponse(response)
3214
def _ensure_real(self):
3215
self.branch._ensure_real()
3216
if self._real_store is None:
3217
self._real_store = config.BranchStore(self.branch)
3220
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
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()
743
def initialize(self, a_bzrdir):
744
assert isinstance(a_bzrdir, RemoteBzrDir)
745
return a_bzrdir.create_branch()
748
class RemoteBranch(branch.Branch):
3221
749
"""Branch stored on a server accessed by HPSS RPC.
3223
751
At the moment most operations are mapped down to simple file operations.
3226
754
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3227
_client=None, format=None, setup_stacking=True, name=None,
3228
possible_transports=None):
3229
756
"""Create a RemoteBranch instance.
3231
758
:param real_branch: An optional local implementation of the branch
3232
759
format, usually accessing the data via the VFS.
3233
760
:param _client: Private parameter for testing.
3234
:param format: A RemoteBranchFormat object, None to create one
3235
automatically. If supplied it should have a network_name already
3237
:param setup_stacking: If True make an RPC call to determine the
3238
stacked (or not) status of the branch. If False assume the branch
3240
:param name: Colocated branch name
3242
762
# We intentionally don't call the parent class's __init__, because it
3243
763
# will try to assign to self.tags, which is a property in this subclass.
3244
764
# And the parent's __init__ doesn't do much anyway.
765
self._revision_history_cache = None
3245
766
self.bzrdir = remote_bzrdir
3246
767
if _client is not None:
3247
768
self._client = _client
3249
self._client = remote_bzrdir._client
770
self._client = client._SmartClient(self.bzrdir._medium)
3250
771
self.repository = remote_repository
3251
772
if real_branch is not None:
3252
773
self._real_branch = real_branch
3389
825
self.bzrdir, self._client)
3390
826
return self._control_files
828
def _get_checkout_format(self):
830
return self._real_branch._get_checkout_format()
3392
832
def get_physical_lock_status(self):
3393
833
"""See Branch.get_physical_lock_status()."""
3395
response = self._client.call('Branch.get_physical_lock_status',
3396
self._remote_path())
3397
except errors.UnknownSmartMethod:
3399
return self._real_branch.get_physical_lock_status()
3400
if response[0] not in ('yes', 'no'):
3401
raise errors.UnexpectedSmartServerResponse(response)
3402
return (response[0] == 'yes')
3404
def get_stacked_on_url(self):
3405
"""Get the URL this branch is stacked against.
3407
:raises NotStacked: If the branch is not stacked.
3408
:raises UnstackableBranchFormat: If the branch does not support
3410
:raises UnstackableRepositoryFormat: If the repository does not support
3414
# there may not be a repository yet, so we can't use
3415
# self._translate_error, so we can't use self._call either.
3416
response = self._client.call('Branch.get_stacked_on_url',
3417
self._remote_path())
3418
except errors.ErrorFromSmartServer, err:
3419
# there may not be a repository yet, so we can't call through
3420
# its _translate_error
3421
_translate_error(err, branch=self)
3422
except errors.UnknownSmartMethod, err:
3424
return self._real_branch.get_stacked_on_url()
3425
if response[0] != 'ok':
3426
raise errors.UnexpectedSmartServerResponse(response)
3429
def set_stacked_on_url(self, url):
3430
branch.Branch.set_stacked_on_url(self, url)
3432
self._is_stacked = False
3434
self._is_stacked = True
3436
def _vfs_get_tags_bytes(self):
3438
return self._real_branch._get_tags_bytes()
3441
def _get_tags_bytes(self):
3442
if self._tags_bytes is None:
3443
self._tags_bytes = self._get_tags_bytes_via_hpss()
3444
return self._tags_bytes
3446
def _get_tags_bytes_via_hpss(self):
3447
medium = self._client._medium
3448
if medium._is_remote_before((1, 13)):
3449
return self._vfs_get_tags_bytes()
3451
response = self._call('Branch.get_tags_bytes', self._remote_path())
3452
except errors.UnknownSmartMethod:
3453
medium._remember_remote_is_before((1, 13))
3454
return self._vfs_get_tags_bytes()
3457
def _vfs_set_tags_bytes(self, bytes):
3459
return self._real_branch._set_tags_bytes(bytes)
3461
def _set_tags_bytes(self, bytes):
3462
if self.is_locked():
3463
self._tags_bytes = bytes
3464
medium = self._client._medium
3465
if medium._is_remote_before((1, 18)):
3466
self._vfs_set_tags_bytes(bytes)
3470
self._remote_path(), self._lock_token, self._repo_lock_token)
3471
response = self._call_with_body_bytes(
3472
'Branch.set_tags_bytes', args, bytes)
3473
except errors.UnknownSmartMethod:
3474
medium._remember_remote_is_before((1, 18))
3475
self._vfs_set_tags_bytes(bytes)
834
# should be an API call to the server, as branches must be lockable.
836
return self._real_branch.get_physical_lock_status()
3477
838
def lock_read(self):
3478
"""Lock the branch for read operations.
3480
:return: A bzrlib.lock.LogicalLockResult.
3482
self.repository.lock_read()
3483
839
if not self._lock_mode:
3484
self._note_lock('r')
3485
840
self._lock_mode = 'r'
3486
841
self._lock_count = 1
3487
842
if self._real_branch is not None:
3488
843
self._real_branch.lock_read()
3490
845
self._lock_count += 1
3491
return lock.LogicalLockResult(self.unlock)
3493
847
def _remote_lock_write(self, token):
3494
848
if token is None:
3495
849
branch_token = repo_token = ''
3497
851
branch_token = token
3498
repo_token = self.repository.lock_write().repository_token
852
repo_token = self.repository.lock_write()
3499
853
self.repository.unlock()
3500
err_context = {'token': token}
3502
response = self._call(
3503
'Branch.lock_write', self._remote_path(), branch_token,
3504
repo_token or '', **err_context)
3505
except errors.LockContention, e:
3506
# The LockContention from the server doesn't have any
3507
# information about the lock_url. We re-raise LockContention
3508
# with valid lock_url.
3509
raise errors.LockContention('(remote lock)',
3510
self.repository.base.split('.bzr/')[0])
3511
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)
3512
869
raise errors.UnexpectedSmartServerResponse(response)
3513
ok, branch_token, repo_token = response
3514
return branch_token, repo_token
3516
871
def lock_write(self, token=None):
3517
872
if not self._lock_mode:
3518
self._note_lock('w')
3519
# Lock the branch and repo in one remote call.
3520
873
remote_tokens = self._remote_lock_write(token)
3521
874
self._lock_token, self._repo_lock_token = remote_tokens
3522
if not self._lock_token:
3523
raise SmartProtocolError('Remote server did not return a token!')
3524
# Tell the self.repository object that it is locked.
3525
self.repository.lock_write(
3526
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.
3528
884
if self._real_branch is not None:
3529
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()
3530
891
if token is not None:
3531
892
self._leave_lock = True
894
# XXX: this case seems to be unreachable; token cannot be None.
3533
895
self._leave_lock = False
3534
896
self._lock_mode = 'w'
3535
897
self._lock_count = 1
3536
898
elif self._lock_mode == 'r':
3537
raise errors.ReadOnlyError(self)
899
raise errors.ReadOnlyTransaction
3539
901
if token is not None:
3540
# A token was given to lock_write, and we're relocking, so
3541
# 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.
3543
904
if token != self._lock_token:
3544
905
raise errors.TokenMismatch(token, self._lock_token)
3545
906
self._lock_count += 1
3546
# Re-lock the repository too.
3547
self.repository.lock_write(self._repo_lock_token)
3548
return BranchWriteLockResult(self.unlock, self._lock_token or None)
907
return self._lock_token
3550
909
def _unlock(self, branch_token, repo_token):
3551
err_context = {'token': str((branch_token, repo_token))}
3552
response = self._call(
3553
'Branch.unlock', self._remote_path(), branch_token,
3554
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,
3555
913
if response == ('ok',):
3557
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)
3559
@only_raises(errors.LockNotHeld, errors.LockBroken)
3560
921
def unlock(self):
3562
self._lock_count -= 1
3563
if not self._lock_count:
3564
self._clear_cached_state()
3565
mode = self._lock_mode
3566
self._lock_mode = None
3567
if self._real_branch is not None:
3568
if (not self._leave_lock and mode == 'w' and
3569
self._repo_lock_token):
3570
# If this RemoteBranch will remove the physical lock
3571
# for the repository, make sure the _real_branch
3572
# doesn't do it first. (Because the _real_branch's
3573
# repository is set to be the RemoteRepository.)
3574
self._real_branch.repository.leave_lock_in_place()
3575
self._real_branch.unlock()
3577
# Only write-locked branched need to make a remote method
3578
# call to perform the unlock.
3580
if not self._lock_token:
3581
raise AssertionError('Locked, but no token!')
3582
branch_token = self._lock_token
3583
repo_token = self._repo_lock_token
3584
self._lock_token = None
3585
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:
3586
928
if not self._leave_lock:
3587
self._unlock(branch_token, repo_token)
3589
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)
3591
947
def break_lock(self):
3593
response = self._call(
3594
'Branch.break_lock', self._remote_path())
3595
except errors.UnknownSmartMethod:
3597
return self._real_branch.break_lock()
3598
if response != ('ok',):
3599
raise errors.UnexpectedSmartServerResponse(response)
949
return self._real_branch.break_lock()
3601
951
def leave_lock_in_place(self):
3602
if not self._lock_token:
3603
raise NotImplementedError(self.leave_lock_in_place)
3604
952
self._leave_lock = True
3606
954
def dont_leave_lock_in_place(self):
3607
if not self._lock_token:
3608
raise NotImplementedError(self.dont_leave_lock_in_place)
3609
955
self._leave_lock = False
3612
def get_rev_id(self, revno, history=None):
3614
return _mod_revision.NULL_REVISION
3615
last_revision_info = self.last_revision_info()
3616
ok, result = self.repository.get_rev_id_for_revno(
3617
revno, last_revision_info)
3620
missing_parent = result[1]
3621
# Either the revision named by the server is missing, or its parent
3622
# is. Call get_parent_map to determine which, so that we report a
3624
parent_map = self.repository.get_parent_map([missing_parent])
3625
if missing_parent in parent_map:
3626
missing_parent = parent_map[missing_parent]
3627
raise errors.RevisionNotPresent(missing_parent, self.repository)
3629
def _read_last_revision_info(self):
3630
response = self._call('Branch.last_revision_info', self._remote_path())
3631
if response[0] != 'ok':
3632
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,)
3633
962
revno = int(response[1])
3634
963
last_revision = response[2]
3635
964
return (revno, last_revision)
3637
966
def _gen_revision_history(self):
3638
967
"""See Branch._gen_revision_history()."""
3639
if self._is_stacked:
3641
return self._real_branch._gen_revision_history()
3642
response_tuple, response_handler = self._call_expecting_body(
3643
'Branch.revision_history', self._remote_path())
3644
if response_tuple[0] != 'ok':
3645
raise errors.UnexpectedSmartServerResponse(response_tuple)
3646
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')
3647
974
if result == ['']:
3651
def _remote_path(self):
3652
return self.bzrdir._path_for_remote_call(self._client)
3654
def _set_last_revision_descendant(self, revision_id, other_branch,
3655
allow_diverged=False, allow_overwrite_descendant=False):
3656
# This performs additional work to meet the hook contract; while its
3657
# undesirable, we have to synthesise the revno to call the hook, and
3658
# not calling the hook is worse as it means changes can't be prevented.
3659
# Having calculated this though, we can't just call into
3660
# set_last_revision_info as a simple call, because there is a set_rh
3661
# hook that some folk may still be using.
3662
old_revno, old_revid = self.last_revision_info()
3663
history = self._lefthand_history(revision_id)
3664
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3665
err_context = {'other_branch': other_branch}
3666
response = self._call('Branch.set_last_revision_ex',
3667
self._remote_path(), self._lock_token, self._repo_lock_token,
3668
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
3670
self._clear_cached_state()
3671
if len(response) != 3 and response[0] != 'ok':
3672
raise errors.UnexpectedSmartServerResponse(response)
3673
new_revno, new_revision_id = response[1:]
3674
self._last_revision_info_cache = new_revno, new_revision_id
3675
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3676
if self._real_branch is not None:
3677
cache = new_revno, new_revision_id
3678
self._real_branch._last_revision_info_cache = cache
3680
def _set_last_revision(self, revision_id):
3681
old_revno, old_revid = self.last_revision_info()
3682
# This performs additional work to meet the hook contract; while its
3683
# undesirable, we have to synthesise the revno to call the hook, and
3684
# not calling the hook is worse as it means changes can't be prevented.
3685
# Having calculated this though, we can't just call into
3686
# set_last_revision_info as a simple call, because there is a set_rh
3687
# hook that some folk may still be using.
3688
history = self._lefthand_history(revision_id)
3689
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3690
self._clear_cached_state()
3691
response = self._call('Branch.set_last_revision',
3692
self._remote_path(), self._lock_token, self._repo_lock_token,
3694
if response != ('ok',):
3695
raise errors.UnexpectedSmartServerResponse(response)
3696
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3698
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
3699
978
@needs_write_lock
3700
979
def set_revision_history(self, rev_history):
3701
"""See Branch.set_revision_history."""
3702
self._set_revision_history(rev_history)
3705
def _set_revision_history(self, rev_history):
3706
980
# Send just the tip revision of the history; the server will generate
3707
981
# the full history from that. If the revision doesn't exist in this
3708
982
# branch, NoSuchRevision will be raised.
983
path = self.bzrdir._path_for_remote_call(self._client)
3709
984
if rev_history == []:
3710
985
rev_id = 'null:'
3712
987
rev_id = rev_history[-1]
3713
self._set_last_revision(rev_id)
3714
for hook in branch.Branch.hooks['set_rh']:
3715
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,))
3716
996
self._cache_revision_history(rev_history)
3718
def _get_parent_location(self):
3719
medium = self._client._medium
3720
if medium._is_remote_before((1, 13)):
3721
return self._vfs_get_parent_location()
3723
response = self._call('Branch.get_parent', self._remote_path())
3724
except errors.UnknownSmartMethod:
3725
medium._remember_remote_is_before((1, 13))
3726
return self._vfs_get_parent_location()
3727
if len(response) != 1:
3728
raise errors.UnexpectedSmartServerResponse(response)
3729
parent_location = response[0]
3730
if parent_location == '':
3732
return parent_location
3734
def _vfs_get_parent_location(self):
3736
return self._real_branch._get_parent_location()
3738
def _set_parent_location(self, url):
3739
medium = self._client._medium
3740
if medium._is_remote_before((1, 15)):
3741
return self._vfs_set_parent_location(url)
3743
call_url = url or ''
3744
if type(call_url) is not str:
3745
raise AssertionError('url must be a str or None (%s)' % url)
3746
response = self._call('Branch.set_parent_location',
3747
self._remote_path(), self._lock_token, self._repo_lock_token,
3749
except errors.UnknownSmartMethod:
3750
medium._remember_remote_is_before((1, 15))
3751
return self._vfs_set_parent_location(url)
3753
raise errors.UnexpectedSmartServerResponse(response)
3755
def _vfs_set_parent_location(self, url):
3757
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)
3759
1024
@needs_write_lock
3760
1025
def pull(self, source, overwrite=False, stop_revision=None,
3762
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
3763
1032
self._ensure_real()
3764
return self._real_branch.pull(
1033
self._real_branch.pull(
3765
1034
source, overwrite=overwrite, stop_revision=stop_revision,
3766
_override_hook_target=self, **kwargs)
3768
1037
@needs_read_lock
3769
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
1038
def push(self, target, overwrite=False, stop_revision=None):
3770
1039
self._ensure_real()
3771
1040
return self._real_branch.push(
3772
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
1041
target, overwrite=overwrite, stop_revision=stop_revision,
3773
1042
_override_hook_source_branch=self)
3775
1044
def is_locked(self):
3776
1045
return self._lock_count >= 1
3779
def revision_id_to_dotted_revno(self, revision_id):
3780
"""Given a revision id, return its dotted revno.
3782
:return: a tuple like (1,) or (400,1,3).
3785
response = self._call('Branch.revision_id_to_revno',
3786
self._remote_path(), revision_id)
3787
except errors.UnknownSmartMethod:
3789
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3790
if response[0] == 'ok':
3791
return tuple([int(x) for x in response[1:]])
3793
raise errors.UnexpectedSmartServerResponse(response)
3796
def revision_id_to_revno(self, revision_id):
3797
"""Given a revision id on the branch mainline, return its revno.
3802
response = self._call('Branch.revision_id_to_revno',
3803
self._remote_path(), revision_id)
3804
except errors.UnknownSmartMethod:
3806
return self._real_branch.revision_id_to_revno(revision_id)
3807
if response[0] == 'ok':
3808
if len(response) == 2:
3809
return int(response[1])
3810
raise NoSuchRevision(self, revision_id)
3812
raise errors.UnexpectedSmartServerResponse(response)
3815
1047
def set_last_revision_info(self, revno, revision_id):
3816
# XXX: These should be returned by the set_last_revision_info verb
3817
old_revno, old_revid = self.last_revision_info()
3818
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3819
if not revision_id or not isinstance(revision_id, basestring):
3820
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3822
response = self._call('Branch.set_last_revision_info',
3823
self._remote_path(), self._lock_token, self._repo_lock_token,
3824
str(revno), revision_id)
3825
except errors.UnknownSmartMethod:
3827
self._clear_cached_state_of_remote_branch_only()
3828
self._real_branch.set_last_revision_info(revno, revision_id)
3829
self._last_revision_info_cache = revno, revision_id
3831
if response == ('ok',):
3832
self._clear_cached_state()
3833
self._last_revision_info_cache = revno, revision_id
3834
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3835
# Update the _real_branch's cache too.
3836
if self._real_branch is not None:
3837
cache = self._last_revision_info_cache
3838
self._real_branch._last_revision_info_cache = cache
3840
raise errors.UnexpectedSmartServerResponse(response)
1049
self._clear_cached_state()
1050
return self._real_branch.set_last_revision_info(revno, revision_id)
3843
1052
def generate_revision_history(self, revision_id, last_rev=None,
3844
1053
other_branch=None):
3845
medium = self._client._medium
3846
if not medium._is_remote_before((1, 6)):
3847
# Use a smart method for 1.6 and above servers
3849
self._set_last_revision_descendant(revision_id, other_branch,
3850
allow_diverged=True, allow_overwrite_descendant=True)
3852
except errors.UnknownSmartMethod:
3853
medium._remember_remote_is_before((1, 6))
3854
self._clear_cached_state_of_remote_branch_only()
3855
self._set_revision_history(self._lefthand_history(revision_id,
3856
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
3858
1063
def set_push_location(self, location):
3859
1064
self._ensure_real()
3860
1065
return self._real_branch.set_push_location(location)
3862
def heads_to_fetch(self):
3863
if self._format._use_default_local_heads_to_fetch():
3864
# We recognise this format, and its heads-to-fetch implementation
3865
# is the default one (tip + tags). In this case it's cheaper to
3866
# just use the default implementation rather than a special RPC as
3867
# the tip and tags data is cached.
3868
return branch.Branch.heads_to_fetch(self)
3869
medium = self._client._medium
3870
if medium._is_remote_before((2, 4)):
3871
return self._vfs_heads_to_fetch()
3873
return self._rpc_heads_to_fetch()
3874
except errors.UnknownSmartMethod:
3875
medium._remember_remote_is_before((2, 4))
3876
return self._vfs_heads_to_fetch()
3878
def _rpc_heads_to_fetch(self):
3879
response = self._call('Branch.heads_to_fetch', self._remote_path())
3880
if len(response) != 2:
3881
raise errors.UnexpectedSmartServerResponse(response)
3882
must_fetch, if_present_fetch = response
3883
return set(must_fetch), set(if_present_fetch)
3885
def _vfs_heads_to_fetch(self):
1067
def update_revisions(self, other, stop_revision=None):
3886
1068
self._ensure_real()
3887
return self._real_branch.heads_to_fetch()
3890
class RemoteConfig(object):
3891
"""A Config that reads and writes from smart verbs.
3893
It is a low-level object that considers config data to be name/value pairs
3894
that may be associated with a section. Assigning meaning to the these
3895
values is done at higher levels like bzrlib.config.TreeConfig.
3898
def get_option(self, name, section=None, default=None):
3899
"""Return the value associated with a named option.
3901
:param name: The name of the value
3902
:param section: The section the option is in (if any)
3903
:param default: The value to return if the value is not set
3904
:return: The value or default value
3907
configobj = self._get_configobj()
3910
section_obj = configobj
3913
section_obj = configobj[section]
3916
if section_obj is None:
3919
value = section_obj.get(name, default)
3920
except errors.UnknownSmartMethod:
3921
value = self._vfs_get_option(name, section, default)
3922
for hook in config.OldConfigHooks['get']:
3923
hook(self, name, value)
3926
def _response_to_configobj(self, response):
3927
if len(response[0]) and response[0][0] != 'ok':
3928
raise errors.UnexpectedSmartServerResponse(response)
3929
lines = response[1].read_body_bytes().splitlines()
3930
conf = config.ConfigObj(lines, encoding='utf-8')
3931
for hook in config.OldConfigHooks['load']:
3936
class RemoteBranchConfig(RemoteConfig):
3937
"""A RemoteConfig for Branches."""
3939
def __init__(self, branch):
3940
self._branch = branch
3942
def _get_configobj(self):
3943
path = self._branch._remote_path()
3944
response = self._branch._client.call_expecting_body(
3945
'Branch.get_config_file', path)
3946
return self._response_to_configobj(response)
3948
def set_option(self, value, name, section=None):
3949
"""Set the value associated with a named option.
3951
:param value: The value to set
3952
:param name: The name of the value to set
3953
:param section: The section the option is in (if any)
3955
medium = self._branch._client._medium
3956
if medium._is_remote_before((1, 14)):
3957
return self._vfs_set_option(value, name, section)
3958
if isinstance(value, dict):
3959
if medium._is_remote_before((2, 2)):
3960
return self._vfs_set_option(value, name, section)
3961
return self._set_config_option_dict(value, name, section)
3963
return self._set_config_option(value, name, section)
3965
def _set_config_option(self, value, name, section):
3967
path = self._branch._remote_path()
3968
response = self._branch._client.call('Branch.set_config_option',
3969
path, self._branch._lock_token, self._branch._repo_lock_token,
3970
value.encode('utf8'), name, section or '')
3971
except errors.UnknownSmartMethod:
3972
medium = self._branch._client._medium
3973
medium._remember_remote_is_before((1, 14))
3974
return self._vfs_set_option(value, name, section)
3976
raise errors.UnexpectedSmartServerResponse(response)
3978
def _serialize_option_dict(self, option_dict):
3980
for key, value in option_dict.items():
3981
if isinstance(key, unicode):
3982
key = key.encode('utf8')
3983
if isinstance(value, unicode):
3984
value = value.encode('utf8')
3985
utf8_dict[key] = value
3986
return bencode.bencode(utf8_dict)
3988
def _set_config_option_dict(self, value, name, section):
3990
path = self._branch._remote_path()
3991
serialised_dict = self._serialize_option_dict(value)
3992
response = self._branch._client.call(
3993
'Branch.set_config_option_dict',
3994
path, self._branch._lock_token, self._branch._repo_lock_token,
3995
serialised_dict, name, section or '')
3996
except errors.UnknownSmartMethod:
3997
medium = self._branch._client._medium
3998
medium._remember_remote_is_before((2, 2))
3999
return self._vfs_set_option(value, name, section)
4001
raise errors.UnexpectedSmartServerResponse(response)
4003
def _real_object(self):
4004
self._branch._ensure_real()
4005
return self._branch._real_branch
4007
def _vfs_set_option(self, value, name, section=None):
4008
return self._real_object()._get_config().set_option(
4009
value, name, section)
4012
class RemoteBzrDirConfig(RemoteConfig):
4013
"""A RemoteConfig for BzrDirs."""
4015
def __init__(self, bzrdir):
4016
self._bzrdir = bzrdir
4018
def _get_configobj(self):
4019
medium = self._bzrdir._client._medium
4020
verb = 'BzrDir.get_config_file'
4021
if medium._is_remote_before((1, 15)):
4022
raise errors.UnknownSmartMethod(verb)
4023
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4024
response = self._bzrdir._call_expecting_body(
4026
return self._response_to_configobj(response)
4028
def _vfs_get_option(self, name, section, default):
4029
return self._real_object()._get_config().get_option(
4030
name, section, default)
4032
def set_option(self, value, name, section=None):
4033
"""Set the value associated with a named option.
4035
:param value: The value to set
4036
:param name: The name of the value to set
4037
:param section: The section the option is in (if any)
4039
return self._real_object()._get_config().set_option(
4040
value, name, section)
4042
def _real_object(self):
4043
self._bzrdir._ensure_real()
4044
return self._bzrdir._real_bzrdir
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
4047
1086
def _extract_tar(tar, to_dir):
4052
1091
for tarinfo in tar:
4053
1092
tar.extract(tarinfo, to_dir)
4056
error_translators = registry.Registry()
4057
no_context_error_translators = registry.Registry()
4060
def _translate_error(err, **context):
4061
"""Translate an ErrorFromSmartServer into a more useful error.
4063
Possible context keys:
4071
If the error from the server doesn't match a known pattern, then
4072
UnknownErrorFromSmartServer is raised.
4076
return context[name]
4077
except KeyError, key_err:
4078
mutter('Missing key %r in context %r', key_err.args[0], context)
4081
"""Get the path from the context if present, otherwise use first error
4085
return context['path']
4086
except KeyError, key_err:
4088
return err.error_args[0]
4089
except IndexError, idx_err:
4091
'Missing key %r in context %r', key_err.args[0], context)
4095
translator = error_translators.get(err.error_verb)
4099
raise translator(err, find, get_path)
4101
translator = no_context_error_translators.get(err.error_verb)
4103
raise errors.UnknownErrorFromSmartServer(err)
4105
raise translator(err)
4108
error_translators.register('NoSuchRevision',
4109
lambda err, find, get_path: NoSuchRevision(
4110
find('branch'), err.error_args[0]))
4111
error_translators.register('nosuchrevision',
4112
lambda err, find, get_path: NoSuchRevision(
4113
find('repository'), err.error_args[0]))
4115
def _translate_nobranch_error(err, find, get_path):
4116
if len(err.error_args) >= 1:
4117
extra = err.error_args[0]
4120
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4123
error_translators.register('nobranch', _translate_nobranch_error)
4124
error_translators.register('norepository',
4125
lambda err, find, get_path: errors.NoRepositoryPresent(
4127
error_translators.register('UnlockableTransport',
4128
lambda err, find, get_path: errors.UnlockableTransport(
4129
find('bzrdir').root_transport))
4130
error_translators.register('TokenMismatch',
4131
lambda err, find, get_path: errors.TokenMismatch(
4132
find('token'), '(remote token)'))
4133
error_translators.register('Diverged',
4134
lambda err, find, get_path: errors.DivergedBranches(
4135
find('branch'), find('other_branch')))
4136
error_translators.register('NotStacked',
4137
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4139
def _translate_PermissionDenied(err, find, get_path):
4141
if len(err.error_args) >= 2:
4142
extra = err.error_args[1]
4145
return errors.PermissionDenied(path, extra=extra)
4147
error_translators.register('PermissionDenied', _translate_PermissionDenied)
4148
error_translators.register('ReadError',
4149
lambda err, find, get_path: errors.ReadError(get_path()))
4150
error_translators.register('NoSuchFile',
4151
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4152
error_translators.register('TokenLockingNotSupported',
4153
lambda err, find, get_path: errors.TokenLockingNotSupported(
4154
find('repository')))
4155
error_translators.register('UnsuspendableWriteGroup',
4156
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4157
repository=find('repository')))
4158
error_translators.register('UnresumableWriteGroup',
4159
lambda err, find, get_path: errors.UnresumableWriteGroup(
4160
repository=find('repository'), write_groups=err.error_args[0],
4161
reason=err.error_args[1]))
4162
no_context_error_translators.register('IncompatibleRepositories',
4163
lambda err: errors.IncompatibleRepositories(
4164
err.error_args[0], err.error_args[1], err.error_args[2]))
4165
no_context_error_translators.register('LockContention',
4166
lambda err: errors.LockContention('(remote lock)'))
4167
no_context_error_translators.register('LockFailed',
4168
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
4169
no_context_error_translators.register('TipChangeRejected',
4170
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4171
no_context_error_translators.register('UnstackableBranchFormat',
4172
lambda err: errors.UnstackableBranchFormat(*err.error_args))
4173
no_context_error_translators.register('UnstackableRepositoryFormat',
4174
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4175
no_context_error_translators.register('FileExists',
4176
lambda err: errors.FileExists(err.error_args[0]))
4177
no_context_error_translators.register('DirectoryNotEmpty',
4178
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
4180
def _translate_short_readv_error(err):
4181
args = err.error_args
4182
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
4185
no_context_error_translators.register('ShortReadvError',
4186
_translate_short_readv_error)
4188
def _translate_unicode_error(err):
4189
encoding = str(err.error_args[0]) # encoding must always be a string
4190
val = err.error_args[1]
4191
start = int(err.error_args[2])
4192
end = int(err.error_args[3])
4193
reason = str(err.error_args[4]) # reason must always be a string
4194
if val.startswith('u:'):
4195
val = val[2:].decode('utf-8')
4196
elif val.startswith('s:'):
4197
val = val[2:].decode('base64')
4198
if err.error_verb == 'UnicodeDecodeError':
4199
raise UnicodeDecodeError(encoding, val, start, end, reason)
4200
elif err.error_verb == 'UnicodeEncodeError':
4201
raise UnicodeEncodeError(encoding, val, start, end, reason)
4203
no_context_error_translators.register('UnicodeEncodeError',
4204
_translate_unicode_error)
4205
no_context_error_translators.register('UnicodeDecodeError',
4206
_translate_unicode_error)
4207
no_context_error_translators.register('ReadOnlyError',
4208
lambda err: errors.TransportNotPossible('readonly transport'))
4209
no_context_error_translators.register('MemoryError',
4210
lambda err: errors.BzrError("remote server out of memory\n"
4211
"Retry non-remotely, or contact the server admin for details."))
4212
no_context_error_translators.register('RevisionNotPresent',
4213
lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
4215
no_context_error_translators.register('BzrCheckError',
4216
lambda err: errors.BzrCheckError(msg=err.error_args[0]))