13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
20
from cStringIO import StringIO
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
from __future__ import absolute_import
22
22
from bzrlib import (
25
bzrdir as _mod_bzrdir,
26
config as _mod_config,
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
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
33
54
from bzrlib.lockable_files import LockableFiles
55
from bzrlib.smart import client, vfs, repository as smart_repo
56
from bzrlib.smart.client import _SmartClient
34
57
from bzrlib.revision import NULL_REVISION
35
from bzrlib.smart import client, vfs
36
from bzrlib.symbol_versioning import (
40
from bzrlib.trace import note
42
# Note: RemoteBzrDirFormat is in bzrdir.py
44
class RemoteBzrDir(BzrDir):
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
62
from bzrlib.versionedfile import FulltextContentFactory
65
_DEFAULT_SEARCH_DEPTH = 100
68
class _RpcHelper(object):
69
"""Mixin class that helps with issuing RPCs."""
71
def _call(self, method, *args, **err_context):
73
return self._client.call(method, *args)
74
except errors.ErrorFromSmartServer, err:
75
self._translate_error(err, **err_context)
77
def _call_expecting_body(self, method, *args, **err_context):
79
return self._client.call_expecting_body(method, *args)
80
except errors.ErrorFromSmartServer, err:
81
self._translate_error(err, **err_context)
83
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
85
return self._client.call_with_body_bytes(method, args, body_bytes)
86
except errors.ErrorFromSmartServer, err:
87
self._translate_error(err, **err_context)
89
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
92
return self._client.call_with_body_bytes_expecting_body(
93
method, args, body_bytes)
94
except errors.ErrorFromSmartServer, err:
95
self._translate_error(err, **err_context)
98
def response_tuple_to_repo_format(response):
99
"""Convert a response tuple describing a repository format to a format."""
100
format = RemoteRepositoryFormat()
101
format._rich_root_data = (response[0] == 'yes')
102
format._supports_tree_reference = (response[1] == 'yes')
103
format._supports_external_lookups = (response[2] == 'yes')
104
format._network_name = response[3]
108
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
109
# does not have to be imported unless a remote format is involved.
111
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
112
"""Format representing bzrdirs accessed via a smart server"""
114
supports_workingtrees = False
116
colocated_branches = False
119
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
120
# XXX: It's a bit ugly that the network name is here, because we'd
121
# like to believe that format objects are stateless or at least
122
# immutable, However, we do at least avoid mutating the name after
123
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
124
self._network_name = None
127
return "%s(_network_name=%r)" % (self.__class__.__name__,
130
def get_format_description(self):
131
if self._network_name:
133
real_format = controldir.network_format_registry.get(
138
return 'Remote: ' + real_format.get_format_description()
139
return 'bzr remote bzrdir'
141
def get_format_string(self):
142
raise NotImplementedError(self.get_format_string)
144
def network_name(self):
145
if self._network_name:
146
return self._network_name
148
raise AssertionError("No network name set.")
150
def initialize_on_transport(self, transport):
152
# hand off the request to the smart server
153
client_medium = transport.get_smart_medium()
154
except errors.NoSmartMedium:
155
# TODO: lookup the local format from a server hint.
156
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
157
return local_dir_format.initialize_on_transport(transport)
158
client = _SmartClient(client_medium)
159
path = client.remote_path_from_transport(transport)
161
response = client.call('BzrDirFormat.initialize', path)
162
except errors.ErrorFromSmartServer, err:
163
_translate_error(err, path=path)
164
if response[0] != 'ok':
165
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
166
format = RemoteBzrDirFormat()
167
self._supply_sub_formats_to(format)
168
return RemoteBzrDir(transport, format)
170
def parse_NoneTrueFalse(self, arg):
177
raise AssertionError("invalid arg %r" % arg)
179
def _serialize_NoneTrueFalse(self, arg):
186
def _serialize_NoneString(self, arg):
189
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
190
create_prefix=False, force_new_repo=False, stacked_on=None,
191
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
194
# hand off the request to the smart server
195
client_medium = transport.get_smart_medium()
196
except errors.NoSmartMedium:
199
# Decline to open it if the server doesn't support our required
200
# version (3) so that the VFS-based transport will do it.
201
if client_medium.should_probe():
203
server_version = client_medium.protocol_version()
204
if server_version != '2':
208
except errors.SmartProtocolError:
209
# Apparently there's no usable smart server there, even though
210
# the medium supports the smart protocol.
215
client = _SmartClient(client_medium)
216
path = client.remote_path_from_transport(transport)
217
if client_medium._is_remote_before((1, 16)):
220
# TODO: lookup the local format from a server hint.
221
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
222
self._supply_sub_formats_to(local_dir_format)
223
return local_dir_format.initialize_on_transport_ex(transport,
224
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
225
force_new_repo=force_new_repo, stacked_on=stacked_on,
226
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
227
make_working_trees=make_working_trees, shared_repo=shared_repo,
229
return self._initialize_on_transport_ex_rpc(client, path, transport,
230
use_existing_dir, create_prefix, force_new_repo, stacked_on,
231
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
233
def _initialize_on_transport_ex_rpc(self, client, path, transport,
234
use_existing_dir, create_prefix, force_new_repo, stacked_on,
235
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
237
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
238
args.append(self._serialize_NoneTrueFalse(create_prefix))
239
args.append(self._serialize_NoneTrueFalse(force_new_repo))
240
args.append(self._serialize_NoneString(stacked_on))
241
# stack_on_pwd is often/usually our transport
244
stack_on_pwd = transport.relpath(stack_on_pwd)
247
except errors.PathNotChild:
249
args.append(self._serialize_NoneString(stack_on_pwd))
250
args.append(self._serialize_NoneString(repo_format_name))
251
args.append(self._serialize_NoneTrueFalse(make_working_trees))
252
args.append(self._serialize_NoneTrueFalse(shared_repo))
253
request_network_name = self._network_name or \
254
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
256
response = client.call('BzrDirFormat.initialize_ex_1.16',
257
request_network_name, path, *args)
258
except errors.UnknownSmartMethod:
259
client._medium._remember_remote_is_before((1,16))
260
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
261
self._supply_sub_formats_to(local_dir_format)
262
return local_dir_format.initialize_on_transport_ex(transport,
263
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
264
force_new_repo=force_new_repo, stacked_on=stacked_on,
265
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
266
make_working_trees=make_working_trees, shared_repo=shared_repo,
268
except errors.ErrorFromSmartServer, err:
269
_translate_error(err, path=path)
270
repo_path = response[0]
271
bzrdir_name = response[6]
272
require_stacking = response[7]
273
require_stacking = self.parse_NoneTrueFalse(require_stacking)
274
format = RemoteBzrDirFormat()
275
format._network_name = bzrdir_name
276
self._supply_sub_formats_to(format)
277
bzrdir = RemoteBzrDir(transport, format, _client=client)
279
repo_format = response_tuple_to_repo_format(response[1:])
283
repo_bzrdir_format = RemoteBzrDirFormat()
284
repo_bzrdir_format._network_name = response[5]
285
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
289
final_stack = response[8] or None
290
final_stack_pwd = response[9] or None
292
final_stack_pwd = urlutils.join(
293
transport.base, final_stack_pwd)
294
remote_repo = RemoteRepository(repo_bzr, repo_format)
295
if len(response) > 10:
296
# Updated server verb that locks remotely.
297
repo_lock_token = response[10] or None
298
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
300
remote_repo.dont_leave_lock_in_place()
302
remote_repo.lock_write()
303
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
304
final_stack_pwd, require_stacking)
305
policy.acquire_repository()
309
bzrdir._format.set_branch_format(self.get_branch_format())
311
# The repo has already been created, but we need to make sure that
312
# we'll make a stackable branch.
313
bzrdir._format.require_stacking(_skip_repo=True)
314
return remote_repo, bzrdir, require_stacking, policy
316
def _open(self, transport):
317
return RemoteBzrDir(transport, self)
319
def __eq__(self, other):
320
if not isinstance(other, RemoteBzrDirFormat):
322
return self.get_format_description() == other.get_format_description()
324
def __return_repository_format(self):
325
# Always return a RemoteRepositoryFormat object, but if a specific bzr
326
# repository format has been asked for, tell the RemoteRepositoryFormat
327
# that it should use that for init() etc.
328
result = RemoteRepositoryFormat()
329
custom_format = getattr(self, '_repository_format', None)
331
if isinstance(custom_format, RemoteRepositoryFormat):
334
# We will use the custom format to create repositories over the
335
# wire; expose its details like rich_root_data for code to
337
result._custom_format = custom_format
340
def get_branch_format(self):
341
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
342
if not isinstance(result, RemoteBranchFormat):
343
new_result = RemoteBranchFormat()
344
new_result._custom_format = result
346
self.set_branch_format(new_result)
350
repository_format = property(__return_repository_format,
351
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
354
class RemoteControlStore(_mod_config.IniFileStore):
355
"""Control store which attempts to use HPSS calls to retrieve control store.
357
Note that this is specific to bzr-based formats.
360
def __init__(self, bzrdir):
361
super(RemoteControlStore, self).__init__()
363
self._real_store = None
365
def lock_write(self, token=None):
367
return self._real_store.lock_write(token)
371
return self._real_store.unlock()
375
# We need to be able to override the undecorated implementation
376
self.save_without_locking()
378
def save_without_locking(self):
379
super(RemoteControlStore, self).save()
381
def _ensure_real(self):
382
self.bzrdir._ensure_real()
383
if self._real_store is None:
384
self._real_store = _mod_config.ControlStore(self.bzrdir)
386
def external_url(self):
387
return self.bzrdir.user_url
389
def _load_content(self):
390
medium = self.bzrdir._client._medium
391
path = self.bzrdir._path_for_remote_call(self.bzrdir._client)
393
response, handler = self.bzrdir._call_expecting_body(
394
'BzrDir.get_config_file', path)
395
except errors.UnknownSmartMethod:
397
return self._real_store._load_content()
398
if len(response) and response[0] != 'ok':
399
raise errors.UnexpectedSmartServerResponse(response)
400
return handler.read_body_bytes()
402
def _save_content(self, content):
403
# FIXME JRV 2011-11-22: Ideally this should use a
404
# HPSS call too, but at the moment it is not possible
405
# to write lock control directories.
407
return self._real_store._save_content(content)
410
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
45
411
"""Control directory on a remote server, accessed via bzr:// or similar."""
47
def __init__(self, transport, _client=None):
413
def __init__(self, transport, format, _client=None, _force_probe=False):
48
414
"""Construct a RemoteBzrDir.
50
416
:param _client: Private parameter for testing. Disables probing and the
51
417
use of a real bzrdir.
53
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
419
_mod_bzrdir.BzrDir.__init__(self, transport, format)
54
420
# this object holds a delegated bzrdir that uses file-level operations
55
421
# to talk to the other side
56
422
self._real_bzrdir = None
423
self._has_working_tree = None
424
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
425
# create_branch for details.
426
self._next_open_branch_result = None
58
428
if _client is None:
59
self._shared_medium = transport.get_shared_medium()
60
self._client = client._SmartClient(self._shared_medium)
429
medium = transport.get_smart_medium()
430
self._client = client._SmartClient(medium)
62
432
self._client = _client
63
self._shared_medium = None
439
return '%s(%r)' % (self.__class__.__name__, self._client)
441
def _probe_bzrdir(self):
442
medium = self._client._medium
66
443
path = self._path_for_remote_call(self._client)
67
response = self._client.call('BzrDir.open', path)
444
if medium._is_remote_before((2, 1)):
448
self._rpc_open_2_1(path)
450
except errors.UnknownSmartMethod:
451
medium._remember_remote_is_before((2, 1))
454
def _rpc_open_2_1(self, path):
455
response = self._call('BzrDir.open_2.1', path)
456
if response == ('no',):
457
raise errors.NotBranchError(path=self.root_transport.base)
458
elif response[0] == 'yes':
459
if response[1] == 'yes':
460
self._has_working_tree = True
461
elif response[1] == 'no':
462
self._has_working_tree = False
464
raise errors.UnexpectedSmartServerResponse(response)
466
raise errors.UnexpectedSmartServerResponse(response)
468
def _rpc_open(self, path):
469
response = self._call('BzrDir.open', path)
68
470
if response not in [('yes',), ('no',)]:
69
471
raise errors.UnexpectedSmartServerResponse(response)
70
472
if response == ('no',):
71
raise errors.NotBranchError(path=transport.base)
473
raise errors.NotBranchError(path=self.root_transport.base)
73
475
def _ensure_real(self):
74
476
"""Ensure that there is a _real_bzrdir set.
76
478
Used before calls to self._real_bzrdir.
78
480
if not self._real_bzrdir:
79
self._real_bzrdir = BzrDir.open_from_transport(
80
self.root_transport, _server_formats=False)
481
if 'hpssvfs' in debug.debug_flags:
483
warning('VFS BzrDir access triggered\n%s',
484
''.join(traceback.format_stack()))
485
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
486
self.root_transport, probers=[_mod_bzrdir.BzrProber])
487
self._format._network_name = \
488
self._real_bzrdir._format.network_name()
490
def _translate_error(self, err, **context):
491
_translate_error(err, bzrdir=self, **context)
493
def break_lock(self):
494
# Prevent aliasing problems in the next_open_branch_result cache.
495
# See create_branch for rationale.
496
self._next_open_branch_result = None
497
return _mod_bzrdir.BzrDir.break_lock(self)
499
def _vfs_checkout_metadir(self):
501
return self._real_bzrdir.checkout_metadir()
503
def checkout_metadir(self):
504
"""Retrieve the controldir format to use for checkouts of this one.
506
medium = self._client._medium
507
if medium._is_remote_before((2, 5)):
508
return self._vfs_checkout_metadir()
509
path = self._path_for_remote_call(self._client)
511
response = self._client.call('BzrDir.checkout_metadir',
513
except errors.UnknownSmartMethod:
514
medium._remember_remote_is_before((2, 5))
515
return self._vfs_checkout_metadir()
516
if len(response) != 3:
517
raise errors.UnexpectedSmartServerResponse(response)
518
control_name, repo_name, branch_name = response
520
format = controldir.network_format_registry.get(control_name)
522
raise errors.UnknownFormatError(kind='control',
526
repo_format = _mod_repository.network_format_registry.get(
529
raise errors.UnknownFormatError(kind='repository',
531
format.repository_format = repo_format
534
format.set_branch_format(
535
branch.network_format_registry.get(branch_name))
537
raise errors.UnknownFormatError(kind='branch',
541
def _vfs_cloning_metadir(self, require_stacking=False):
543
return self._real_bzrdir.cloning_metadir(
544
require_stacking=require_stacking)
546
def cloning_metadir(self, require_stacking=False):
547
medium = self._client._medium
548
if medium._is_remote_before((1, 13)):
549
return self._vfs_cloning_metadir(require_stacking=require_stacking)
550
verb = 'BzrDir.cloning_metadir'
555
path = self._path_for_remote_call(self._client)
557
response = self._call(verb, path, stacking)
558
except errors.UnknownSmartMethod:
559
medium._remember_remote_is_before((1, 13))
560
return self._vfs_cloning_metadir(require_stacking=require_stacking)
561
except errors.UnknownErrorFromSmartServer, err:
562
if err.error_tuple != ('BranchReference',):
564
# We need to resolve the branch reference to determine the
565
# cloning_metadir. This causes unnecessary RPCs to open the
566
# referenced branch (and bzrdir, etc) but only when the caller
567
# didn't already resolve the branch reference.
568
referenced_branch = self.open_branch()
569
return referenced_branch.bzrdir.cloning_metadir()
570
if len(response) != 3:
571
raise errors.UnexpectedSmartServerResponse(response)
572
control_name, repo_name, branch_info = response
573
if len(branch_info) != 2:
574
raise errors.UnexpectedSmartServerResponse(response)
575
branch_ref, branch_name = branch_info
577
format = controldir.network_format_registry.get(control_name)
579
raise errors.UnknownFormatError(kind='control', format=control_name)
583
format.repository_format = _mod_repository.network_format_registry.get(
586
raise errors.UnknownFormatError(kind='repository',
588
if branch_ref == 'ref':
589
# XXX: we need possible_transports here to avoid reopening the
590
# connection to the referenced location
591
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
592
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
593
format.set_branch_format(branch_format)
594
elif branch_ref == 'branch':
597
branch_format = branch.network_format_registry.get(
600
raise errors.UnknownFormatError(kind='branch',
602
format.set_branch_format(branch_format)
604
raise errors.UnexpectedSmartServerResponse(response)
82
607
def create_repository(self, shared=False):
84
self._real_bzrdir.create_repository(shared=shared)
85
return self.open_repository()
87
def create_branch(self):
89
real_branch = self._real_bzrdir.create_branch()
90
return RemoteBranch(self, self.find_repository(), real_branch)
92
def create_workingtree(self, revision_id=None):
608
# as per meta1 formats - just delegate to the format object which may
610
result = self._format.repository_format.initialize(self, shared)
611
if not isinstance(result, RemoteRepository):
612
return self.open_repository()
616
def destroy_repository(self):
617
"""See BzrDir.destroy_repository"""
618
path = self._path_for_remote_call(self._client)
620
response = self._call('BzrDir.destroy_repository', path)
621
except errors.UnknownSmartMethod:
623
self._real_bzrdir.destroy_repository()
625
if response[0] != 'ok':
626
raise SmartProtocolError('unexpected response code %s' % (response,))
628
def create_branch(self, name=None, repository=None,
629
append_revisions_only=None):
631
name = self._get_selected_branch()
633
raise errors.NoColocatedBranchSupport(self)
634
# as per meta1 formats - just delegate to the format object which may
636
real_branch = self._format.get_branch_format().initialize(self,
637
name=name, repository=repository,
638
append_revisions_only=append_revisions_only)
639
if not isinstance(real_branch, RemoteBranch):
640
if not isinstance(repository, RemoteRepository):
641
raise AssertionError(
642
'need a RemoteRepository to use with RemoteBranch, got %r'
644
result = RemoteBranch(self, repository, real_branch, name=name)
647
# BzrDir.clone_on_transport() uses the result of create_branch but does
648
# not return it to its callers; we save approximately 8% of our round
649
# trips by handing the branch we created back to the first caller to
650
# open_branch rather than probing anew. Long term we need a API in
651
# bzrdir that doesn't discard result objects (like result_branch).
653
self._next_open_branch_result = result
656
def destroy_branch(self, name=None):
657
"""See BzrDir.destroy_branch"""
659
name = self._get_selected_branch()
661
raise errors.NoColocatedBranchSupport(self)
662
path = self._path_for_remote_call(self._client)
668
response = self._call('BzrDir.destroy_branch', path, *args)
669
except errors.UnknownSmartMethod:
671
self._real_bzrdir.destroy_branch(name=name)
672
self._next_open_branch_result = None
674
self._next_open_branch_result = None
675
if response[0] != 'ok':
676
raise SmartProtocolError('unexpected response code %s' % (response,))
678
def create_workingtree(self, revision_id=None, from_branch=None,
679
accelerator_tree=None, hardlink=False):
93
680
raise errors.NotLocalUrl(self.transport.base)
95
def find_branch_format(self):
682
def find_branch_format(self, name=None):
96
683
"""Find the branch 'format' for this bzrdir.
98
685
This might be a synthetic object for e.g. RemoteBranch and SVN.
100
b = self.open_branch()
687
b = self.open_branch(name=name)
103
def get_branch_reference(self):
690
def get_branches(self, possible_transports=None, ignore_fallbacks=False):
691
path = self._path_for_remote_call(self._client)
693
response, handler = self._call_expecting_body(
694
'BzrDir.get_branches', path)
695
except errors.UnknownSmartMethod:
697
return self._real_bzrdir.get_branches()
698
if response[0] != "success":
699
raise errors.UnexpectedSmartServerResponse(response)
700
body = bencode.bdecode(handler.read_body_bytes())
702
for (name, value) in body.iteritems():
703
ret[name] = self._open_branch(name, value[0], value[1],
704
possible_transports=possible_transports,
705
ignore_fallbacks=ignore_fallbacks)
708
def set_branch_reference(self, target_branch, name=None):
709
"""See BzrDir.set_branch_reference()."""
711
name = self._get_selected_branch()
713
raise errors.NoColocatedBranchSupport(self)
715
return self._real_bzrdir.set_branch_reference(target_branch, name=name)
717
def get_branch_reference(self, name=None):
104
718
"""See BzrDir.get_branch_reference()."""
720
name = self._get_selected_branch()
722
raise errors.NoColocatedBranchSupport(self)
723
response = self._get_branch_reference()
724
if response[0] == 'ref':
729
def _get_branch_reference(self):
105
730
path = self._path_for_remote_call(self._client)
106
response = self._client.call('BzrDir.open_branch', path)
107
if response[0] == 'ok':
108
if response[1] == '':
109
# branch at this location.
112
# a branch reference, use the existing BranchReference logic.
114
elif response == ('nobranch',):
115
raise errors.NotBranchError(path=self.root_transport.base)
731
medium = self._client._medium
733
('BzrDir.open_branchV3', (2, 1)),
734
('BzrDir.open_branchV2', (1, 13)),
735
('BzrDir.open_branch', None),
737
for verb, required_version in candidate_calls:
738
if required_version and medium._is_remote_before(required_version):
741
response = self._call(verb, path)
742
except errors.UnknownSmartMethod:
743
if required_version is None:
745
medium._remember_remote_is_before(required_version)
748
if verb == 'BzrDir.open_branch':
749
if response[0] != 'ok':
750
raise errors.UnexpectedSmartServerResponse(response)
751
if response[1] != '':
752
return ('ref', response[1])
754
return ('branch', '')
755
if response[0] not in ('ref', 'branch'):
117
756
raise errors.UnexpectedSmartServerResponse(response)
119
def open_branch(self, _unsupported=False):
120
assert _unsupported == False, 'unsupported flag support not implemented yet.'
121
reference_url = self.get_branch_reference()
122
if reference_url is None:
123
# branch at this location.
124
return RemoteBranch(self, self.find_repository())
759
def _get_tree_branch(self, name=None):
760
"""See BzrDir._get_tree_branch()."""
761
return None, self.open_branch(name=name)
763
def _open_branch(self, name, kind, location_or_format,
764
ignore_fallbacks=False, possible_transports=None):
126
766
# a branch reference, use the existing BranchReference logic.
127
767
format = BranchReferenceFormat()
128
return format.open(self, _found=True, location=reference_url)
768
return format.open(self, name=name, _found=True,
769
location=location_or_format, ignore_fallbacks=ignore_fallbacks,
770
possible_transports=possible_transports)
771
branch_format_name = location_or_format
772
if not branch_format_name:
773
branch_format_name = None
774
format = RemoteBranchFormat(network_name=branch_format_name)
775
return RemoteBranch(self, self.find_repository(), format=format,
776
setup_stacking=not ignore_fallbacks, name=name,
777
possible_transports=possible_transports)
779
def open_branch(self, name=None, unsupported=False,
780
ignore_fallbacks=False, possible_transports=None):
782
name = self._get_selected_branch()
784
raise errors.NoColocatedBranchSupport(self)
786
raise NotImplementedError('unsupported flag support not implemented yet.')
787
if self._next_open_branch_result is not None:
788
# See create_branch for details.
789
result = self._next_open_branch_result
790
self._next_open_branch_result = None
792
response = self._get_branch_reference()
793
return self._open_branch(name, response[0], response[1],
794
possible_transports=possible_transports,
795
ignore_fallbacks=ignore_fallbacks)
797
def _open_repo_v1(self, path):
798
verb = 'BzrDir.find_repository'
799
response = self._call(verb, path)
800
if response[0] != 'ok':
801
raise errors.UnexpectedSmartServerResponse(response)
802
# servers that only support the v1 method don't support external
805
repo = self._real_bzrdir.open_repository()
806
response = response + ('no', repo._format.network_name())
807
return response, repo
809
def _open_repo_v2(self, path):
810
verb = 'BzrDir.find_repositoryV2'
811
response = self._call(verb, path)
812
if response[0] != 'ok':
813
raise errors.UnexpectedSmartServerResponse(response)
815
repo = self._real_bzrdir.open_repository()
816
response = response + (repo._format.network_name(),)
817
return response, repo
819
def _open_repo_v3(self, path):
820
verb = 'BzrDir.find_repositoryV3'
821
medium = self._client._medium
822
if medium._is_remote_before((1, 13)):
823
raise errors.UnknownSmartMethod(verb)
825
response = self._call(verb, path)
826
except errors.UnknownSmartMethod:
827
medium._remember_remote_is_before((1, 13))
829
if response[0] != 'ok':
830
raise errors.UnexpectedSmartServerResponse(response)
831
return response, None
130
833
def open_repository(self):
131
834
path = self._path_for_remote_call(self._client)
132
response = self._client.call('BzrDir.find_repository', path)
133
assert response[0] in ('ok', 'norepository'), \
134
'unexpected response code %s' % (response,)
135
if response[0] == 'norepository':
136
raise errors.NoRepositoryPresent(self)
137
assert len(response) == 4, 'incorrect response length %s' % (response,)
836
for probe in [self._open_repo_v3, self._open_repo_v2,
839
response, real_repo = probe(path)
841
except errors.UnknownSmartMethod:
844
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
845
if response[0] != 'ok':
846
raise errors.UnexpectedSmartServerResponse(response)
847
if len(response) != 6:
848
raise SmartProtocolError('incorrect response length %s' % (response,))
138
849
if response[1] == '':
139
format = RemoteRepositoryFormat()
140
format.rich_root_data = (response[2] == 'yes')
141
format.supports_tree_reference = (response[3] == 'yes')
142
return RemoteRepository(self, format)
850
# repo is at this dir.
851
format = response_tuple_to_repo_format(response[2:])
852
# Used to support creating a real format instance when needed.
853
format._creating_bzrdir = self
854
remote_repo = RemoteRepository(self, format)
855
format._creating_repo = remote_repo
856
if real_repo is not None:
857
remote_repo._set_real_repository(real_repo)
144
860
raise errors.NoRepositoryPresent(self)
862
def has_workingtree(self):
863
if self._has_working_tree is None:
864
path = self._path_for_remote_call(self._client)
866
response = self._call('BzrDir.has_workingtree', path)
867
except errors.UnknownSmartMethod:
869
self._has_working_tree = self._real_bzrdir.has_workingtree()
871
if response[0] not in ('yes', 'no'):
872
raise SmartProtocolError('unexpected response code %s' % (response,))
873
self._has_working_tree = (response[0] == 'yes')
874
return self._has_working_tree
146
876
def open_workingtree(self, recommend_upgrade=True):
148
if self._real_bzrdir.has_workingtree():
877
if self.has_workingtree():
149
878
raise errors.NotLocalUrl(self.root_transport)
151
880
raise errors.NoWorkingTree(self.root_transport.base)
153
882
def _path_for_remote_call(self, client):
154
883
"""Return the path to be used for this bzrdir in a remote call."""
155
return client.remote_path_from_transport(self.root_transport)
884
return urlutils.split_segment_parameters_raw(
885
client.remote_path_from_transport(self.root_transport))[0]
157
def get_branch_transport(self, branch_format):
887
def get_branch_transport(self, branch_format, name=None):
158
888
self._ensure_real()
159
return self._real_bzrdir.get_branch_transport(branch_format)
889
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
161
891
def get_repository_transport(self, repository_format):
162
892
self._ensure_real()
170
900
"""Upgrading of remote bzrdirs is not supported yet."""
173
def needs_format_conversion(self, format=None):
903
def needs_format_conversion(self, format):
174
904
"""Upgrading of remote bzrdirs is not supported yet."""
177
def clone(self, url, revision_id=None, force_new_repo=False):
179
return self._real_bzrdir.clone(url, revision_id=revision_id,
180
force_new_repo=force_new_repo)
183
class RemoteRepositoryFormat(repository.RepositoryFormat):
907
def _get_config(self):
908
return RemoteBzrDirConfig(self)
910
def _get_config_store(self):
911
return RemoteControlStore(self)
914
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
184
915
"""Format for repositories accessed over a _SmartClient.
186
917
Instances of this repository are represented by RemoteRepository
189
The RemoteRepositoryFormat is parameterised during construction
920
The RemoteRepositoryFormat is parameterized during construction
190
921
to reflect the capabilities of the real, remote format. Specifically
191
922
the attributes rich_root_data and supports_tree_reference are set
192
923
on a per instance basis, and are not set (and should not be) at
926
:ivar _custom_format: If set, a specific concrete repository format that
927
will be used when initializing a repository with this
928
RemoteRepositoryFormat.
929
:ivar _creating_repo: If set, the repository object that this
930
RemoteRepositoryFormat was created for: it can be called into
931
to obtain data like the network name.
196
_matchingbzrdir = RemoteBzrDirFormat
934
_matchingbzrdir = RemoteBzrDirFormat()
935
supports_full_versioned_files = True
936
supports_leaving_lock = True
939
_mod_repository.RepositoryFormat.__init__(self)
940
self._custom_format = None
941
self._network_name = None
942
self._creating_bzrdir = None
943
self._revision_graph_can_have_wrong_parents = None
944
self._supports_chks = None
945
self._supports_external_lookups = None
946
self._supports_tree_reference = None
947
self._supports_funky_characters = None
948
self._supports_nesting_repositories = None
949
self._rich_root_data = None
952
return "%s(_network_name=%r)" % (self.__class__.__name__,
956
def fast_deltas(self):
958
return self._custom_format.fast_deltas
961
def rich_root_data(self):
962
if self._rich_root_data is None:
964
self._rich_root_data = self._custom_format.rich_root_data
965
return self._rich_root_data
968
def supports_chks(self):
969
if self._supports_chks is None:
971
self._supports_chks = self._custom_format.supports_chks
972
return self._supports_chks
975
def supports_external_lookups(self):
976
if self._supports_external_lookups is None:
978
self._supports_external_lookups = \
979
self._custom_format.supports_external_lookups
980
return self._supports_external_lookups
983
def supports_funky_characters(self):
984
if self._supports_funky_characters is None:
986
self._supports_funky_characters = \
987
self._custom_format.supports_funky_characters
988
return self._supports_funky_characters
991
def supports_nesting_repositories(self):
992
if self._supports_nesting_repositories is None:
994
self._supports_nesting_repositories = \
995
self._custom_format.supports_nesting_repositories
996
return self._supports_nesting_repositories
999
def supports_tree_reference(self):
1000
if self._supports_tree_reference is None:
1002
self._supports_tree_reference = \
1003
self._custom_format.supports_tree_reference
1004
return self._supports_tree_reference
1007
def revision_graph_can_have_wrong_parents(self):
1008
if self._revision_graph_can_have_wrong_parents is None:
1010
self._revision_graph_can_have_wrong_parents = \
1011
self._custom_format.revision_graph_can_have_wrong_parents
1012
return self._revision_graph_can_have_wrong_parents
1014
def _vfs_initialize(self, a_bzrdir, shared):
1015
"""Helper for common code in initialize."""
1016
if self._custom_format:
1017
# Custom format requested
1018
result = self._custom_format.initialize(a_bzrdir, shared=shared)
1019
elif self._creating_bzrdir is not None:
1020
# Use the format that the repository we were created to back
1022
prior_repo = self._creating_bzrdir.open_repository()
1023
prior_repo._ensure_real()
1024
result = prior_repo._real_repository._format.initialize(
1025
a_bzrdir, shared=shared)
1027
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
1028
# support remote initialization.
1029
# We delegate to a real object at this point (as RemoteBzrDir
1030
# delegate to the repository format which would lead to infinite
1031
# recursion if we just called a_bzrdir.create_repository.
1032
a_bzrdir._ensure_real()
1033
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
1034
if not isinstance(result, RemoteRepository):
1035
return self.open(a_bzrdir)
198
1039
def initialize(self, a_bzrdir, shared=False):
199
assert isinstance(a_bzrdir, RemoteBzrDir), \
200
'%r is not a RemoteBzrDir' % (a_bzrdir,)
201
return a_bzrdir.create_repository(shared=shared)
1040
# Being asked to create on a non RemoteBzrDir:
1041
if not isinstance(a_bzrdir, RemoteBzrDir):
1042
return self._vfs_initialize(a_bzrdir, shared)
1043
medium = a_bzrdir._client._medium
1044
if medium._is_remote_before((1, 13)):
1045
return self._vfs_initialize(a_bzrdir, shared)
1046
# Creating on a remote bzr dir.
1047
# 1) get the network name to use.
1048
if self._custom_format:
1049
network_name = self._custom_format.network_name()
1050
elif self._network_name:
1051
network_name = self._network_name
1053
# Select the current bzrlib default and ask for that.
1054
reference_bzrdir_format = controldir.format_registry.get('default')()
1055
reference_format = reference_bzrdir_format.repository_format
1056
network_name = reference_format.network_name()
1057
# 2) try direct creation via RPC
1058
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
1059
verb = 'BzrDir.create_repository'
1063
shared_str = 'False'
1065
response = a_bzrdir._call(verb, path, network_name, shared_str)
1066
except errors.UnknownSmartMethod:
1067
# Fallback - use vfs methods
1068
medium._remember_remote_is_before((1, 13))
1069
return self._vfs_initialize(a_bzrdir, shared)
1071
# Turn the response into a RemoteRepository object.
1072
format = response_tuple_to_repo_format(response[1:])
1073
# Used to support creating a real format instance when needed.
1074
format._creating_bzrdir = a_bzrdir
1075
remote_repo = RemoteRepository(a_bzrdir, format)
1076
format._creating_repo = remote_repo
203
1079
def open(self, a_bzrdir):
204
assert isinstance(a_bzrdir, RemoteBzrDir)
1080
if not isinstance(a_bzrdir, RemoteBzrDir):
1081
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
205
1082
return a_bzrdir.open_repository()
1084
def _ensure_real(self):
1085
if self._custom_format is None:
1087
self._custom_format = _mod_repository.network_format_registry.get(
1090
raise errors.UnknownFormatError(kind='repository',
1091
format=self._network_name)
1094
def _fetch_order(self):
1096
return self._custom_format._fetch_order
1099
def _fetch_uses_deltas(self):
1101
return self._custom_format._fetch_uses_deltas
1104
def _fetch_reconcile(self):
1106
return self._custom_format._fetch_reconcile
207
1108
def get_format_description(self):
208
return 'bzr remote repository'
1110
return 'Remote: ' + self._custom_format.get_format_description()
210
1112
def __eq__(self, other):
211
return self.__class__ == other.__class__
213
def check_conversion_target(self, target_format):
214
if self.rich_root_data and not target_format.rich_root_data:
215
raise errors.BadConversionTarget(
216
'Does not support rich root data.', target_format)
217
if (self.supports_tree_reference and
218
not getattr(target_format, 'supports_tree_reference', False)):
219
raise errors.BadConversionTarget(
220
'Does not support nested trees', target_format)
223
class RemoteRepository(object):
1113
return self.__class__ is other.__class__
1115
def network_name(self):
1116
if self._network_name:
1117
return self._network_name
1118
self._creating_repo._ensure_real()
1119
return self._creating_repo._real_repository._format.network_name()
1122
def pack_compresses(self):
1124
return self._custom_format.pack_compresses
1127
def _serializer(self):
1129
return self._custom_format._serializer
1132
class RemoteRepository(_mod_repository.Repository, _RpcHelper,
1133
lock._RelockDebugMixin):
224
1134
"""Repository accessed over rpc.
226
1136
For the moment most operations are performed using local transport-backed
244
1154
self._real_repository = None
245
1155
self.bzrdir = remote_bzrdir
246
1156
if _client is None:
247
self._client = client._SmartClient(self.bzrdir._shared_medium)
1157
self._client = remote_bzrdir._client
249
1159
self._client = _client
250
1160
self._format = format
251
1161
self._lock_mode = None
252
1162
self._lock_token = None
1163
self._write_group_tokens = None
253
1164
self._lock_count = 0
254
1165
self._leave_lock = False
256
self._reconcile_does_inventory_gc = True
258
def abort_write_group(self):
1166
# Cache of revision parents; misses are cached during read locks, and
1167
# write locks when no _real_repository has been set.
1168
self._unstacked_provider = graph.CachingParentsProvider(
1169
get_parent_map=self._get_parent_map_rpc)
1170
self._unstacked_provider.disable_cache()
1172
# These depend on the actual remote format, so force them off for
1173
# maximum compatibility. XXX: In future these should depend on the
1174
# remote repository instance, but this is irrelevant until we perform
1175
# reconcile via an RPC call.
1176
self._reconcile_does_inventory_gc = False
1177
self._reconcile_fixes_text_parents = False
1178
self._reconcile_backsup_inventory = False
1179
self.base = self.bzrdir.transport.base
1180
# Additional places to query for data.
1181
self._fallback_repositories = []
1184
def user_transport(self):
1185
return self.bzrdir.user_transport
1188
def control_transport(self):
1189
# XXX: Normally you shouldn't directly get at the remote repository
1190
# transport, but I'm not sure it's worth making this method
1191
# optional -- mbp 2010-04-21
1192
return self.bzrdir.get_repository_transport(None)
1195
return "%s(%s)" % (self.__class__.__name__, self.base)
1199
def abort_write_group(self, suppress_errors=False):
259
1200
"""Complete a write group on the decorated repository.
261
Smart methods peform operations in a single step so this api
1202
Smart methods perform operations in a single step so this API
262
1203
is not really applicable except as a compatibility thunk
263
1204
for older plugins that don't use e.g. the CommitBuilder
1207
:param suppress_errors: see Repository.abort_write_group.
1209
if self._real_repository:
1211
return self._real_repository.abort_write_group(
1212
suppress_errors=suppress_errors)
1213
if not self.is_in_write_group():
1215
mutter('(suppressed) not in write group')
1217
raise errors.BzrError("not in write group")
1218
path = self.bzrdir._path_for_remote_call(self._client)
1220
response = self._call('Repository.abort_write_group', path,
1221
self._lock_token, self._write_group_tokens)
1222
except Exception, exc:
1223
self._write_group = None
1224
if not suppress_errors:
1226
mutter('abort_write_group failed')
1227
log_exception_quietly()
1228
note(gettext('bzr: ERROR (ignored): %s'), exc)
1230
if response != ('ok', ):
1231
raise errors.UnexpectedSmartServerResponse(response)
1232
self._write_group_tokens = None
1235
def chk_bytes(self):
1236
"""Decorate the real repository for now.
1238
In the long term a full blown network facility is needed to avoid
1239
creating a real repository object locally.
266
1241
self._ensure_real()
267
return self._real_repository.abort_write_group()
1242
return self._real_repository.chk_bytes
269
1244
def commit_write_group(self):
270
1245
"""Complete a write group on the decorated repository.
272
Smart methods peform operations in a single step so this api
1247
Smart methods perform operations in a single step so this API
273
1248
is not really applicable except as a compatibility thunk
274
1249
for older plugins that don't use e.g. the CommitBuilder
278
return self._real_repository.commit_write_group()
1252
if self._real_repository:
1254
return self._real_repository.commit_write_group()
1255
if not self.is_in_write_group():
1256
raise errors.BzrError("not in write group")
1257
path = self.bzrdir._path_for_remote_call(self._client)
1258
response = self._call('Repository.commit_write_group', path,
1259
self._lock_token, self._write_group_tokens)
1260
if response != ('ok', ):
1261
raise errors.UnexpectedSmartServerResponse(response)
1262
self._write_group_tokens = None
1263
# Refresh data after writing to the repository.
1266
def resume_write_group(self, tokens):
1267
if self._real_repository:
1268
return self._real_repository.resume_write_group(tokens)
1269
path = self.bzrdir._path_for_remote_call(self._client)
1271
response = self._call('Repository.check_write_group', path,
1272
self._lock_token, tokens)
1273
except errors.UnknownSmartMethod:
1275
return self._real_repository.resume_write_group(tokens)
1276
if response != ('ok', ):
1277
raise errors.UnexpectedSmartServerResponse(response)
1278
self._write_group_tokens = tokens
1280
def suspend_write_group(self):
1281
if self._real_repository:
1282
return self._real_repository.suspend_write_group()
1283
ret = self._write_group_tokens or []
1284
self._write_group_tokens = None
1287
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1289
return self._real_repository.get_missing_parent_inventories(
1290
check_for_missing_texts=check_for_missing_texts)
1292
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1294
return self._real_repository.get_rev_id_for_revno(
1297
def get_rev_id_for_revno(self, revno, known_pair):
1298
"""See Repository.get_rev_id_for_revno."""
1299
path = self.bzrdir._path_for_remote_call(self._client)
1301
if self._client._medium._is_remote_before((1, 17)):
1302
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1303
response = self._call(
1304
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1305
except errors.UnknownSmartMethod:
1306
self._client._medium._remember_remote_is_before((1, 17))
1307
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1308
if response[0] == 'ok':
1309
return True, response[1]
1310
elif response[0] == 'history-incomplete':
1311
known_pair = response[1:3]
1312
for fallback in self._fallback_repositories:
1313
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1318
# Not found in any fallbacks
1319
return False, known_pair
1321
raise errors.UnexpectedSmartServerResponse(response)
280
1323
def _ensure_real(self):
281
1324
"""Ensure that there is a _real_repository set.
283
1326
Used before calls to self._real_repository.
1328
Note that _ensure_real causes many roundtrips to the server which are
1329
not desirable, and prevents the use of smart one-roundtrip RPC's to
1330
perform complex operations (such as accessing parent data, streaming
1331
revisions etc). Adding calls to _ensure_real should only be done when
1332
bringing up new functionality, adding fallbacks for smart methods that
1333
require a fallback path, and never to replace an existing smart method
1334
invocation. If in doubt chat to the bzr network team.
285
if not self._real_repository:
1336
if self._real_repository is None:
1337
if 'hpssvfs' in debug.debug_flags:
1339
warning('VFS Repository access triggered\n%s',
1340
''.join(traceback.format_stack()))
1341
self._unstacked_provider.missing_keys.clear()
286
1342
self.bzrdir._ensure_real()
287
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
288
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
290
def get_revision_graph(self, revision_id=None):
291
"""See Repository.get_revision_graph()."""
1343
self._set_real_repository(
1344
self.bzrdir._real_bzrdir.open_repository())
1346
def _translate_error(self, err, **context):
1347
self.bzrdir._translate_error(err, repository=self, **context)
1349
def find_text_key_references(self):
1350
"""Find the text key references within the repository.
1352
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1353
to whether they were referred to by the inventory of the
1354
revision_id that they contain. The inventory texts from all present
1355
revision ids are assessed to generate this report.
1358
return self._real_repository.find_text_key_references()
1360
def _generate_text_key_index(self):
1361
"""Generate a new text key index for the repository.
1363
This is an expensive function that will take considerable time to run.
1365
:return: A dict mapping (file_id, revision_id) tuples to a list of
1366
parents, also (file_id, revision_id) tuples.
1369
return self._real_repository._generate_text_key_index()
1371
def _get_revision_graph(self, revision_id):
1372
"""Private method for using with old (< 1.2) servers to fallback."""
292
1373
if revision_id is None:
293
1374
revision_id = ''
294
elif revision_id == NULL_REVISION:
1375
elif _mod_revision.is_null(revision_id):
297
1378
path = self.bzrdir._path_for_remote_call(self._client)
298
assert type(revision_id) is str
299
response = self._client.call_expecting_body(
1379
response = self._call_expecting_body(
300
1380
'Repository.get_revision_graph', path, revision_id)
301
if response[0][0] not in ['ok', 'nosuchrevision']:
302
raise errors.UnexpectedSmartServerResponse(response[0])
303
if response[0][0] == 'ok':
304
coded = response[1].read_body_bytes()
306
# no revisions in this repository!
308
lines = coded.split('\n')
311
d = tuple(line.split())
312
revision_graph[d[0]] = d[1:]
314
return revision_graph
316
response_body = response[1].read_body_bytes()
317
assert response_body == ''
318
raise NoSuchRevision(self, revision_id)
1381
response_tuple, response_handler = response
1382
if response_tuple[0] != 'ok':
1383
raise errors.UnexpectedSmartServerResponse(response_tuple)
1384
coded = response_handler.read_body_bytes()
1386
# no revisions in this repository!
1388
lines = coded.split('\n')
1391
d = tuple(line.split())
1392
revision_graph[d[0]] = d[1:]
1394
return revision_graph
1396
def _get_sink(self):
1397
"""See Repository._get_sink()."""
1398
return RemoteStreamSink(self)
1400
def _get_source(self, to_format):
1401
"""Return a source for streaming from this repository."""
1402
return RemoteStreamSource(self, to_format)
1405
def get_file_graph(self):
1406
return graph.Graph(self.texts)
320
1409
def has_revision(self, revision_id):
321
"""See Repository.has_revision()."""
322
if revision_id is None:
323
# The null revision is always present.
325
path = self.bzrdir._path_for_remote_call(self._client)
326
response = self._client.call('Repository.has_revision', path, revision_id)
327
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
328
return response[0] == 'yes'
1410
"""True if this repository has a copy of the revision."""
1411
# Copy of bzrlib.repository.Repository.has_revision
1412
return revision_id in self.has_revisions((revision_id,))
1415
def has_revisions(self, revision_ids):
1416
"""Probe to find out the presence of multiple revisions.
1418
:param revision_ids: An iterable of revision_ids.
1419
:return: A set of the revision_ids that were present.
1421
# Copy of bzrlib.repository.Repository.has_revisions
1422
parent_map = self.get_parent_map(revision_ids)
1423
result = set(parent_map)
1424
if _mod_revision.NULL_REVISION in revision_ids:
1425
result.add(_mod_revision.NULL_REVISION)
1428
def _has_same_fallbacks(self, other_repo):
1429
"""Returns true if the repositories have the same fallbacks."""
1430
# XXX: copied from Repository; it should be unified into a base class
1431
# <https://bugs.launchpad.net/bzr/+bug/401622>
1432
my_fb = self._fallback_repositories
1433
other_fb = other_repo._fallback_repositories
1434
if len(my_fb) != len(other_fb):
1436
for f, g in zip(my_fb, other_fb):
1437
if not f.has_same_location(g):
330
1441
def has_same_location(self, other):
331
return (self.__class__ == other.__class__ and
1442
# TODO: Move to RepositoryBase and unify with the regular Repository
1443
# one; unfortunately the tests rely on slightly different behaviour at
1444
# present -- mbp 20090710
1445
return (self.__class__ is other.__class__ and
332
1446
self.bzrdir.transport.base == other.bzrdir.transport.base)
334
1448
def get_graph(self, other_repository=None):
335
1449
"""Return the graph for this repository format"""
336
return self._real_repository.get_graph(other_repository)
1450
parents_provider = self._make_parents_provider(other_repository)
1451
return graph.Graph(parents_provider)
1454
def get_known_graph_ancestry(self, revision_ids):
1455
"""Return the known graph for a set of revision ids and their ancestors.
1457
st = static_tuple.StaticTuple
1458
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1459
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1460
return graph.GraphThunkIdsToKeys(known_graph)
338
1462
def gather_stats(self, revid=None, committers=None):
339
1463
"""See Repository.gather_stats()."""
340
1464
path = self.bzrdir._path_for_remote_call(self._client)
341
if revid in (None, NULL_REVISION):
1465
# revid can be None to indicate no revisions, not just NULL_REVISION
1466
if revid is None or _mod_revision.is_null(revid):
344
1469
fmt_revid = revid
442
1618
:param repository: The repository to fallback to for non-hpss
443
1619
implemented operations.
445
assert not isinstance(repository, RemoteRepository)
1621
if self._real_repository is not None:
1622
# Replacing an already set real repository.
1623
# We cannot do this [currently] if the repository is locked -
1624
# synchronised state might be lost.
1625
if self.is_locked():
1626
raise AssertionError('_real_repository is already set')
1627
if isinstance(repository, RemoteRepository):
1628
raise AssertionError()
446
1629
self._real_repository = repository
1630
# three code paths happen here:
1631
# 1) old servers, RemoteBranch.open() calls _ensure_real before setting
1632
# up stacking. In this case self._fallback_repositories is [], and the
1633
# real repo is already setup. Preserve the real repo and
1634
# RemoteRepository.add_fallback_repository will avoid adding
1636
# 2) new servers, RemoteBranch.open() sets up stacking, and when
1637
# ensure_real is triggered from a branch, the real repository to
1638
# set already has a matching list with separate instances, but
1639
# as they are also RemoteRepositories we don't worry about making the
1640
# lists be identical.
1641
# 3) new servers, RemoteRepository.ensure_real is triggered before
1642
# RemoteBranch.ensure real, in this case we get a repo with no fallbacks
1643
# and need to populate it.
1644
if (self._fallback_repositories and
1645
len(self._real_repository._fallback_repositories) !=
1646
len(self._fallback_repositories)):
1647
if len(self._real_repository._fallback_repositories):
1648
raise AssertionError(
1649
"cannot cleanly remove existing _fallback_repositories")
1650
for fb in self._fallback_repositories:
1651
self._real_repository.add_fallback_repository(fb)
447
1652
if self._lock_mode == 'w':
448
1653
# if we are already locked, the real repository must be able to
449
1654
# acquire the lock with our token.
450
1655
self._real_repository.lock_write(self._lock_token)
451
1656
elif self._lock_mode == 'r':
452
1657
self._real_repository.lock_read()
1658
if self._write_group_tokens is not None:
1659
# if we are already in a write group, resume it
1660
self._real_repository.resume_write_group(self._write_group_tokens)
1661
self._write_group_tokens = None
454
1663
def start_write_group(self):
455
1664
"""Start a write group on the decorated repository.
457
Smart methods peform operations in a single step so this api
1666
Smart methods perform operations in a single step so this API
458
1667
is not really applicable except as a compatibility thunk
459
1668
for older plugins that don't use e.g. the CommitBuilder
463
return self._real_repository.start_write_group()
1671
if self._real_repository:
1673
return self._real_repository.start_write_group()
1674
if not self.is_write_locked():
1675
raise errors.NotWriteLocked(self)
1676
if self._write_group_tokens is not None:
1677
raise errors.BzrError('already in a write group')
1678
path = self.bzrdir._path_for_remote_call(self._client)
1680
response = self._call('Repository.start_write_group', path,
1682
except (errors.UnknownSmartMethod, errors.UnsuspendableWriteGroup):
1684
return self._real_repository.start_write_group()
1685
if response[0] != 'ok':
1686
raise errors.UnexpectedSmartServerResponse(response)
1687
self._write_group_tokens = response[1]
465
1689
def _unlock(self, token):
466
1690
path = self.bzrdir._path_for_remote_call(self._client)
467
response = self._client.call('Repository.unlock', path, token)
1692
# with no token the remote repository is not persistently locked.
1694
err_context = {'token': token}
1695
response = self._call('Repository.unlock', path, token,
468
1697
if response == ('ok',):
470
elif response[0] == 'TokenMismatch':
471
raise errors.TokenMismatch(token, '(remote token)')
473
1700
raise errors.UnexpectedSmartServerResponse(response)
1702
@only_raises(errors.LockNotHeld, errors.LockBroken)
475
1703
def unlock(self):
476
if self._lock_count == 1 and self._lock_mode == 'w':
477
# don't unlock if inside a write group.
478
if self.is_in_write_group():
479
raise errors.BzrError(
480
'Must end write groups before releasing write locks.')
1704
if not self._lock_count:
1705
return lock.cant_unlock_not_held(self)
481
1706
self._lock_count -= 1
482
if not self._lock_count:
483
mode = self._lock_mode
484
self._lock_mode = None
1707
if self._lock_count > 0:
1709
self._unstacked_provider.disable_cache()
1710
old_mode = self._lock_mode
1711
self._lock_mode = None
1713
# The real repository is responsible at present for raising an
1714
# exception if it's in an unfinished write group. However, it
1715
# normally will *not* actually remove the lock from disk - that's
1716
# done by the server on receiving the Repository.unlock call.
1717
# This is just to let the _real_repository stay up to date.
485
1718
if self._real_repository is not None:
486
1719
self._real_repository.unlock()
1720
elif self._write_group_tokens is not None:
1721
self.abort_write_group()
1723
# The rpc-level lock should be released even if there was a
1724
# problem releasing the vfs-based lock.
488
1726
# Only write-locked repositories need to make a remote method
489
# call to perfom the unlock.
491
assert self._lock_token, 'Locked, but no token!'
492
token = self._lock_token
493
self._lock_token = None
494
if not self._leave_lock:
1727
# call to perform the unlock.
1728
old_token = self._lock_token
1729
self._lock_token = None
1730
if not self._leave_lock:
1731
self._unlock(old_token)
1732
# Fallbacks are always 'lock_read()' so we don't pay attention to
1734
for repo in self._fallback_repositories:
497
1737
def break_lock(self):
498
1738
# should hand off to the network
500
return self._real_repository.break_lock()
1739
path = self.bzrdir._path_for_remote_call(self._client)
1741
response = self._call("Repository.break_lock", path)
1742
except errors.UnknownSmartMethod:
1744
return self._real_repository.break_lock()
1745
if response != ('ok',):
1746
raise errors.UnexpectedSmartServerResponse(response)
502
1748
def _get_tarball(self, compression):
503
"""Return a TemporaryFile containing a repository tarball"""
1749
"""Return a TemporaryFile containing a repository tarball.
1751
Returns None if the server does not support sending tarballs.
505
1754
path = self.bzrdir._path_for_remote_call(self._client)
506
response, protocol = self._client.call_expecting_body(
507
'Repository.tarball', path, compression)
508
assert response[0] in ('ok', 'failure'), \
509
'unexpected response code %s' % (response,)
1756
response, protocol = self._call_expecting_body(
1757
'Repository.tarball', path, compression)
1758
except errors.UnknownSmartMethod:
1759
protocol.cancel_read_body()
510
1761
if response[0] == 'ok':
511
1762
# Extract the tarball and return it
512
1763
t = tempfile.NamedTemporaryFile()
514
1765
t.write(protocol.read_body_bytes())
1768
raise errors.UnexpectedSmartServerResponse(response)
1771
def sprout(self, to_bzrdir, revision_id=None):
1772
"""Create a descendent repository for new development.
1774
Unlike clone, this does not copy the settings of the repository.
1776
dest_repo = self._create_sprouting_repo(to_bzrdir, shared=False)
1777
dest_repo.fetch(self, revision_id=revision_id)
1780
def _create_sprouting_repo(self, a_bzrdir, shared):
1781
if not isinstance(a_bzrdir._format, self.bzrdir._format.__class__):
1782
# use target default format.
1783
dest_repo = a_bzrdir.create_repository()
518
raise errors.SmartServerError(error_code=response)
520
def sprout(self, to_bzrdir, revision_id=None):
521
# TODO: Option to control what format is created?
522
to_repo = to_bzrdir.create_repository()
523
self._copy_repository_tarball(to_repo, revision_id)
1785
# Most control formats need the repository to be specifically
1786
# created, but on some old all-in-one formats it's not needed
1788
dest_repo = self._format.initialize(a_bzrdir, shared=shared)
1789
except errors.UninitializableFormat:
1790
dest_repo = a_bzrdir.open_repository()
526
1793
### These methods are just thin shims to the VFS object for now.
528
1796
def revision_tree(self, revision_id):
530
return self._real_repository.revision_tree(revision_id)
1797
revision_id = _mod_revision.ensure_null(revision_id)
1798
if revision_id == _mod_revision.NULL_REVISION:
1799
return InventoryRevisionTree(self,
1800
Inventory(root_id=None), _mod_revision.NULL_REVISION)
1802
return list(self.revision_trees([revision_id]))[0]
532
1804
def get_serializer_format(self):
534
return self._real_repository.get_serializer_format()
1805
path = self.bzrdir._path_for_remote_call(self._client)
1807
response = self._call('VersionedFileRepository.get_serializer_format',
1809
except errors.UnknownSmartMethod:
1811
return self._real_repository.get_serializer_format()
1812
if response[0] != 'ok':
1813
raise errors.UnexpectedSmartServerResponse(response)
536
1816
def get_commit_builder(self, branch, parents, config, timestamp=None,
537
1817
timezone=None, committer=None, revprops=None,
539
# FIXME: It ought to be possible to call this without immediately
540
# triggering _ensure_real. For now it's the easiest thing to do.
542
builder = self._real_repository.get_commit_builder(branch, parents,
543
config, timestamp=timestamp, timezone=timezone,
544
committer=committer, revprops=revprops, revision_id=revision_id)
545
# Make the builder use this RemoteRepository rather than the real one.
546
builder.repository = self
1818
revision_id=None, lossy=False):
1819
"""Obtain a CommitBuilder for this repository.
1821
:param branch: Branch to commit to.
1822
:param parents: Revision ids of the parents of the new revision.
1823
:param config: Configuration to use.
1824
:param timestamp: Optional timestamp recorded for commit.
1825
:param timezone: Optional timezone for timestamp.
1826
:param committer: Optional committer to set for commit.
1827
:param revprops: Optional dictionary of revision properties.
1828
:param revision_id: Optional revision id.
1829
:param lossy: Whether to discard data that can not be natively
1830
represented, when pushing to a foreign VCS
1832
if self._fallback_repositories and not self._format.supports_chks:
1833
raise errors.BzrError("Cannot commit directly to a stacked branch"
1834
" in pre-2a formats. See "
1835
"https://bugs.launchpad.net/bzr/+bug/375013 for details.")
1836
if self._format.rich_root_data:
1837
commit_builder_kls = vf_repository.VersionedFileRootCommitBuilder
1839
commit_builder_kls = vf_repository.VersionedFileCommitBuilder
1840
result = commit_builder_kls(self, parents, config,
1841
timestamp, timezone, committer, revprops, revision_id,
1843
self.start_write_group()
1846
def add_fallback_repository(self, repository):
1847
"""Add a repository to use for looking up data not held locally.
1849
:param repository: A repository.
1851
if not self._format.supports_external_lookups:
1852
raise errors.UnstackableRepositoryFormat(
1853
self._format.network_name(), self.base)
1854
# We need to accumulate additional repositories here, to pass them in
1857
# Make the check before we lock: this raises an exception.
1858
self._check_fallback_repository(repository)
1859
if self.is_locked():
1860
# We will call fallback.unlock() when we transition to the unlocked
1861
# state, so always add a lock here. If a caller passes us a locked
1862
# repository, they are responsible for unlocking it later.
1863
repository.lock_read()
1864
self._fallback_repositories.append(repository)
1865
# If self._real_repository was parameterised already (e.g. because a
1866
# _real_branch had its get_stacked_on_url method called), then the
1867
# repository to be added may already be in the _real_repositories list.
1868
if self._real_repository is not None:
1869
fallback_locations = [repo.user_url for repo in
1870
self._real_repository._fallback_repositories]
1871
if repository.user_url not in fallback_locations:
1872
self._real_repository.add_fallback_repository(repository)
1874
def _check_fallback_repository(self, repository):
1875
"""Check that this repository can fallback to repository safely.
1877
Raise an error if not.
1879
:param repository: A repository to fallback to.
1881
return _mod_repository.InterRepository._assert_same_model(
550
1884
def add_inventory(self, revid, inv, parents):
551
1885
self._ensure_real()
552
1886
return self._real_repository.add_inventory(revid, inv, parents)
555
def add_revision(self, rev_id, rev, inv=None, config=None):
1888
def add_inventory_by_delta(self, basis_revision_id, delta, new_revision_id,
1889
parents, basis_inv=None, propagate_caches=False):
556
1890
self._ensure_real()
557
return self._real_repository.add_revision(
558
rev_id, rev, inv=inv, config=config)
1891
return self._real_repository.add_inventory_by_delta(basis_revision_id,
1892
delta, new_revision_id, parents, basis_inv=basis_inv,
1893
propagate_caches=propagate_caches)
1895
def add_revision(self, revision_id, rev, inv=None):
1896
_mod_revision.check_not_reserved_id(revision_id)
1897
key = (revision_id,)
1898
# check inventory present
1899
if not self.inventories.get_parent_map([key]):
1901
raise errors.WeaveRevisionNotPresent(revision_id,
1904
# yes, this is not suitable for adding with ghosts.
1905
rev.inventory_sha1 = self.add_inventory(revision_id, inv,
1908
rev.inventory_sha1 = self.inventories.get_sha1s([key])[key]
1909
self._add_revision(rev)
1911
def _add_revision(self, rev):
1912
if self._real_repository is not None:
1913
return self._real_repository._add_revision(rev)
1914
text = self._serializer.write_revision_to_string(rev)
1915
key = (rev.revision_id,)
1916
parents = tuple((parent,) for parent in rev.parent_ids)
1917
self._write_group_tokens, missing_keys = self._get_sink().insert_stream(
1918
[('revisions', [FulltextContentFactory(key, parents, None, text)])],
1919
self._format, self._write_group_tokens)
560
1921
@needs_read_lock
561
1922
def get_inventory(self, revision_id):
1923
return list(self.iter_inventories([revision_id]))[0]
1925
def _iter_inventories_rpc(self, revision_ids, ordering):
1926
if ordering is None:
1927
ordering = 'unordered'
1928
path = self.bzrdir._path_for_remote_call(self._client)
1929
body = "\n".join(revision_ids)
1930
response_tuple, response_handler = (
1931
self._call_with_body_bytes_expecting_body(
1932
"VersionedFileRepository.get_inventories",
1933
(path, ordering), body))
1934
if response_tuple[0] != "ok":
1935
raise errors.UnexpectedSmartServerResponse(response_tuple)
1936
deserializer = inventory_delta.InventoryDeltaDeserializer()
1937
byte_stream = response_handler.read_streamed_body()
1938
decoded = smart_repo._byte_stream_to_stream(byte_stream)
1940
# no results whatsoever
1942
src_format, stream = decoded
1943
if src_format.network_name() != self._format.network_name():
1944
raise AssertionError(
1945
"Mismatched RemoteRepository and stream src %r, %r" % (
1946
src_format.network_name(), self._format.network_name()))
1947
# ignore the src format, it's not really relevant
1948
prev_inv = Inventory(root_id=None,
1949
revision_id=_mod_revision.NULL_REVISION)
1950
# there should be just one substream, with inventory deltas
1951
substream_kind, substream = stream.next()
1952
if substream_kind != "inventory-deltas":
1953
raise AssertionError(
1954
"Unexpected stream %r received" % substream_kind)
1955
for record in substream:
1956
(parent_id, new_id, versioned_root, tree_references, invdelta) = (
1957
deserializer.parse_text_bytes(record.get_bytes_as("fulltext")))
1958
if parent_id != prev_inv.revision_id:
1959
raise AssertionError("invalid base %r != %r" % (parent_id,
1960
prev_inv.revision_id))
1961
inv = prev_inv.create_by_apply_delta(invdelta, new_id)
1962
yield inv, inv.revision_id
1965
def _iter_inventories_vfs(self, revision_ids, ordering=None):
562
1966
self._ensure_real()
563
return self._real_repository.get_inventory(revision_id)
1967
return self._real_repository._iter_inventories(revision_ids, ordering)
1969
def iter_inventories(self, revision_ids, ordering=None):
1970
"""Get many inventories by revision_ids.
1972
This will buffer some or all of the texts used in constructing the
1973
inventories in memory, but will only parse a single inventory at a
1976
:param revision_ids: The expected revision ids of the inventories.
1977
:param ordering: optional ordering, e.g. 'topological'. If not
1978
specified, the order of revision_ids will be preserved (by
1979
buffering if necessary).
1980
:return: An iterator of inventories.
1982
if ((None in revision_ids)
1983
or (_mod_revision.NULL_REVISION in revision_ids)):
1984
raise ValueError('cannot get null revision inventory')
1985
for inv, revid in self._iter_inventories(revision_ids, ordering):
1987
raise errors.NoSuchRevision(self, revid)
1990
def _iter_inventories(self, revision_ids, ordering=None):
1991
if len(revision_ids) == 0:
1993
missing = set(revision_ids)
1994
if ordering is None:
1995
order_as_requested = True
1997
order = list(revision_ids)
1999
next_revid = order.pop()
2001
order_as_requested = False
2002
if ordering != 'unordered' and self._fallback_repositories:
2003
raise ValueError('unsupported ordering %r' % ordering)
2004
iter_inv_fns = [self._iter_inventories_rpc] + [
2005
fallback._iter_inventories for fallback in
2006
self._fallback_repositories]
2008
for iter_inv in iter_inv_fns:
2009
request = [revid for revid in revision_ids if revid in missing]
2010
for inv, revid in iter_inv(request, ordering):
2013
missing.remove(inv.revision_id)
2014
if ordering != 'unordered':
2018
if order_as_requested:
2019
# Yield as many results as we can while preserving order.
2020
while next_revid in invs:
2021
inv = invs.pop(next_revid)
2022
yield inv, inv.revision_id
2024
next_revid = order.pop()
2026
# We still want to fully consume the stream, just
2027
# in case it is not actually finished at this point
2030
except errors.UnknownSmartMethod:
2031
for inv, revid in self._iter_inventories_vfs(revision_ids, ordering):
2035
if order_as_requested:
2036
if next_revid is not None:
2037
yield None, next_revid
2040
yield invs.get(revid), revid
2043
yield None, missing.pop()
565
2045
@needs_read_lock
566
2046
def get_revision(self, revision_id):
568
return self._real_repository.get_revision(revision_id)
571
def weave_store(self):
573
return self._real_repository.weave_store
2047
return self.get_revisions([revision_id])[0]
575
2049
def get_transaction(self):
576
2050
self._ensure_real()
579
2053
@needs_read_lock
580
2054
def clone(self, a_bzrdir, revision_id=None):
582
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
2055
dest_repo = self._create_sprouting_repo(
2056
a_bzrdir, shared=self.is_shared())
2057
self.copy_content_into(dest_repo, revision_id)
584
2060
def make_working_trees(self):
585
"""RemoteRepositories never create working trees by default."""
588
def fetch(self, source, revision_id=None, pb=None):
590
return self._real_repository.fetch(
591
source, revision_id=revision_id, pb=pb)
2061
"""See Repository.make_working_trees"""
2062
path = self.bzrdir._path_for_remote_call(self._client)
2064
response = self._call('Repository.make_working_trees', path)
2065
except errors.UnknownSmartMethod:
2067
return self._real_repository.make_working_trees()
2068
if response[0] not in ('yes', 'no'):
2069
raise SmartProtocolError('unexpected response code %s' % (response,))
2070
return response[0] == 'yes'
2072
def refresh_data(self):
2073
"""Re-read any data needed to synchronise with disk.
2075
This method is intended to be called after another repository instance
2076
(such as one used by a smart server) has inserted data into the
2077
repository. On all repositories this will work outside of write groups.
2078
Some repository formats (pack and newer for bzrlib native formats)
2079
support refresh_data inside write groups. If called inside a write
2080
group on a repository that does not support refreshing in a write group
2081
IsInWriteGroupError will be raised.
2083
if self._real_repository is not None:
2084
self._real_repository.refresh_data()
2085
# Refresh the parents cache for this object
2086
self._unstacked_provider.disable_cache()
2087
self._unstacked_provider.enable_cache()
2089
def revision_ids_to_search_result(self, result_set):
2090
"""Convert a set of revision ids to a graph SearchResult."""
2091
result_parents = set()
2092
for parents in self.get_graph().get_parent_map(
2093
result_set).itervalues():
2094
result_parents.update(parents)
2095
included_keys = result_set.intersection(result_parents)
2096
start_keys = result_set.difference(included_keys)
2097
exclude_keys = result_parents.difference(result_set)
2098
result = vf_search.SearchResult(start_keys, exclude_keys,
2099
len(result_set), result_set)
2103
def search_missing_revision_ids(self, other,
2104
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
2105
find_ghosts=True, revision_ids=None, if_present_ids=None,
2107
"""Return the revision ids that other has that this does not.
2109
These are returned in topological order.
2111
revision_id: only return revision ids included by revision_id.
2113
if symbol_versioning.deprecated_passed(revision_id):
2114
symbol_versioning.warn(
2115
'search_missing_revision_ids(revision_id=...) was '
2116
'deprecated in 2.4. Use revision_ids=[...] instead.',
2117
DeprecationWarning, stacklevel=2)
2118
if revision_ids is not None:
2119
raise AssertionError(
2120
'revision_ids is mutually exclusive with revision_id')
2121
if revision_id is not None:
2122
revision_ids = [revision_id]
2123
inter_repo = _mod_repository.InterRepository.get(other, self)
2124
return inter_repo.search_missing_revision_ids(
2125
find_ghosts=find_ghosts, revision_ids=revision_ids,
2126
if_present_ids=if_present_ids, limit=limit)
2128
def fetch(self, source, revision_id=None, find_ghosts=False,
2130
# No base implementation to use as RemoteRepository is not a subclass
2131
# of Repository; so this is a copy of Repository.fetch().
2132
if fetch_spec is not None and revision_id is not None:
2133
raise AssertionError(
2134
"fetch_spec and revision_id are mutually exclusive.")
2135
if self.is_in_write_group():
2136
raise errors.InternalBzrError(
2137
"May not fetch while in a write group.")
2138
# fast path same-url fetch operations
2139
if (self.has_same_location(source)
2140
and fetch_spec is None
2141
and self._has_same_fallbacks(source)):
2142
# check that last_revision is in 'from' and then return a
2144
if (revision_id is not None and
2145
not _mod_revision.is_null(revision_id)):
2146
self.get_revision(revision_id)
2148
# if there is no specific appropriate InterRepository, this will get
2149
# the InterRepository base class, which raises an
2150
# IncompatibleRepositories when asked to fetch.
2151
inter = _mod_repository.InterRepository.get(source, self)
2152
if (fetch_spec is not None and
2153
not getattr(inter, "supports_fetch_spec", False)):
2154
raise errors.UnsupportedOperation(
2155
"fetch_spec not supported for %r" % inter)
2156
return inter.fetch(revision_id=revision_id,
2157
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
593
2159
def create_bundle(self, target, base, fileobj, format=None):
594
2160
self._ensure_real()
595
2161
self._real_repository.create_bundle(target, base, fileobj, format)
598
def control_weaves(self):
600
return self._real_repository.control_weaves
603
def get_ancestry(self, revision_id, topo_sorted=True):
605
return self._real_repository.get_ancestry(revision_id, topo_sorted)
608
def get_inventory_weave(self):
610
return self._real_repository.get_inventory_weave()
612
2163
def fileids_altered_by_revision_ids(self, revision_ids):
613
2164
self._ensure_real()
614
2165
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
2167
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
2169
return self._real_repository._get_versioned_file_checker(
2170
revisions, revision_versions_cache)
2172
def _iter_files_bytes_rpc(self, desired_files, absent):
2173
path = self.bzrdir._path_for_remote_call(self._client)
2176
for (file_id, revid, identifier) in desired_files:
2177
lines.append("%s\0%s" % (
2178
osutils.safe_file_id(file_id),
2179
osutils.safe_revision_id(revid)))
2180
identifiers.append(identifier)
2181
(response_tuple, response_handler) = (
2182
self._call_with_body_bytes_expecting_body(
2183
"Repository.iter_files_bytes", (path, ), "\n".join(lines)))
2184
if response_tuple != ('ok', ):
2185
response_handler.cancel_read_body()
2186
raise errors.UnexpectedSmartServerResponse(response_tuple)
2187
byte_stream = response_handler.read_streamed_body()
2188
def decompress_stream(start, byte_stream, unused):
2189
decompressor = zlib.decompressobj()
2190
yield decompressor.decompress(start)
2191
while decompressor.unused_data == "":
2193
data = byte_stream.next()
2194
except StopIteration:
2196
yield decompressor.decompress(data)
2197
yield decompressor.flush()
2198
unused.append(decompressor.unused_data)
2201
while not "\n" in unused:
2202
unused += byte_stream.next()
2203
header, rest = unused.split("\n", 1)
2204
args = header.split("\0")
2205
if args[0] == "absent":
2206
absent[identifiers[int(args[3])]] = (args[1], args[2])
2209
elif args[0] == "ok":
2212
raise errors.UnexpectedSmartServerResponse(args)
2214
yield (identifiers[idx],
2215
decompress_stream(rest, byte_stream, unused_chunks))
2216
unused = "".join(unused_chunks)
616
2218
def iter_files_bytes(self, desired_files):
617
2219
"""See Repository.iter_file_bytes.
620
return self._real_repository.iter_files_bytes(desired_files)
2223
for (identifier, bytes_iterator) in self._iter_files_bytes_rpc(
2224
desired_files, absent):
2225
yield identifier, bytes_iterator
2226
for fallback in self._fallback_repositories:
2229
desired_files = [(key[0], key[1], identifier) for
2230
(identifier, key) in absent.iteritems()]
2231
for (identifier, bytes_iterator) in fallback.iter_files_bytes(desired_files):
2232
del absent[identifier]
2233
yield identifier, bytes_iterator
2235
# There may be more missing items, but raise an exception
2237
missing_identifier = absent.keys()[0]
2238
missing_key = absent[missing_identifier]
2239
raise errors.RevisionNotPresent(revision_id=missing_key[1],
2240
file_id=missing_key[0])
2241
except errors.UnknownSmartMethod:
2243
for (identifier, bytes_iterator) in (
2244
self._real_repository.iter_files_bytes(desired_files)):
2245
yield identifier, bytes_iterator
2247
def get_cached_parent_map(self, revision_ids):
2248
"""See bzrlib.CachingParentsProvider.get_cached_parent_map"""
2249
return self._unstacked_provider.get_cached_parent_map(revision_ids)
2251
def get_parent_map(self, revision_ids):
2252
"""See bzrlib.Graph.get_parent_map()."""
2253
return self._make_parents_provider().get_parent_map(revision_ids)
2255
def _get_parent_map_rpc(self, keys):
2256
"""Helper for get_parent_map that performs the RPC."""
2257
medium = self._client._medium
2258
if medium._is_remote_before((1, 2)):
2259
# We already found out that the server can't understand
2260
# Repository.get_parent_map requests, so just fetch the whole
2263
# Note that this reads the whole graph, when only some keys are
2264
# wanted. On this old server there's no way (?) to get them all
2265
# in one go, and the user probably will have seen a warning about
2266
# the server being old anyhow.
2267
rg = self._get_revision_graph(None)
2268
# There is an API discrepancy between get_parent_map and
2269
# get_revision_graph. Specifically, a "key:()" pair in
2270
# get_revision_graph just means a node has no parents. For
2271
# "get_parent_map" it means the node is a ghost. So fix up the
2272
# graph to correct this.
2273
# https://bugs.launchpad.net/bzr/+bug/214894
2274
# There is one other "bug" which is that ghosts in
2275
# get_revision_graph() are not returned at all. But we won't worry
2276
# about that for now.
2277
for node_id, parent_ids in rg.iteritems():
2278
if parent_ids == ():
2279
rg[node_id] = (NULL_REVISION,)
2280
rg[NULL_REVISION] = ()
2285
raise ValueError('get_parent_map(None) is not valid')
2286
if NULL_REVISION in keys:
2287
keys.discard(NULL_REVISION)
2288
found_parents = {NULL_REVISION:()}
2290
return found_parents
2293
# TODO(Needs analysis): We could assume that the keys being requested
2294
# from get_parent_map are in a breadth first search, so typically they
2295
# will all be depth N from some common parent, and we don't have to
2296
# have the server iterate from the root parent, but rather from the
2297
# keys we're searching; and just tell the server the keyspace we
2298
# already have; but this may be more traffic again.
2300
# Transform self._parents_map into a search request recipe.
2301
# TODO: Manage this incrementally to avoid covering the same path
2302
# repeatedly. (The server will have to on each request, but the less
2303
# work done the better).
2305
# Negative caching notes:
2306
# new server sends missing when a request including the revid
2307
# 'include-missing:' is present in the request.
2308
# missing keys are serialised as missing:X, and we then call
2309
# provider.note_missing(X) for-all X
2310
parents_map = self._unstacked_provider.get_cached_map()
2311
if parents_map is None:
2312
# Repository is not locked, so there's no cache.
2314
if _DEFAULT_SEARCH_DEPTH <= 0:
2315
(start_set, stop_keys,
2316
key_count) = vf_search.search_result_from_parent_map(
2317
parents_map, self._unstacked_provider.missing_keys)
2319
(start_set, stop_keys,
2320
key_count) = vf_search.limited_search_result_from_parent_map(
2321
parents_map, self._unstacked_provider.missing_keys,
2322
keys, depth=_DEFAULT_SEARCH_DEPTH)
2323
recipe = ('manual', start_set, stop_keys, key_count)
2324
body = self._serialise_search_recipe(recipe)
2325
path = self.bzrdir._path_for_remote_call(self._client)
2327
if type(key) is not str:
2329
"key %r not a plain string" % (key,))
2330
verb = 'Repository.get_parent_map'
2331
args = (path, 'include-missing:') + tuple(keys)
2333
response = self._call_with_body_bytes_expecting_body(
2335
except errors.UnknownSmartMethod:
2336
# Server does not support this method, so get the whole graph.
2337
# Worse, we have to force a disconnection, because the server now
2338
# doesn't realise it has a body on the wire to consume, so the
2339
# only way to recover is to abandon the connection.
2341
'Server is too old for fast get_parent_map, reconnecting. '
2342
'(Upgrade the server to Bazaar 1.2 to avoid this)')
2344
# To avoid having to disconnect repeatedly, we keep track of the
2345
# fact the server doesn't understand remote methods added in 1.2.
2346
medium._remember_remote_is_before((1, 2))
2347
# Recurse just once and we should use the fallback code.
2348
return self._get_parent_map_rpc(keys)
2349
response_tuple, response_handler = response
2350
if response_tuple[0] not in ['ok']:
2351
response_handler.cancel_read_body()
2352
raise errors.UnexpectedSmartServerResponse(response_tuple)
2353
if response_tuple[0] == 'ok':
2354
coded = bz2.decompress(response_handler.read_body_bytes())
2356
# no revisions found
2358
lines = coded.split('\n')
2361
d = tuple(line.split())
2363
revision_graph[d[0]] = d[1:]
2366
if d[0].startswith('missing:'):
2368
self._unstacked_provider.note_missing_key(revid)
2370
# no parents - so give the Graph result
2372
revision_graph[d[0]] = (NULL_REVISION,)
2373
return revision_graph
622
2375
@needs_read_lock
623
2376
def get_signature_text(self, revision_id):
625
return self._real_repository.get_signature_text(revision_id)
628
def get_revision_graph_with_ghosts(self, revision_ids=None):
630
return self._real_repository.get_revision_graph_with_ghosts(
631
revision_ids=revision_ids)
634
def get_inventory_xml(self, revision_id):
636
return self._real_repository.get_inventory_xml(revision_id)
638
def deserialise_inventory(self, revision_id, xml):
640
return self._real_repository.deserialise_inventory(revision_id, xml)
2377
path = self.bzrdir._path_for_remote_call(self._client)
2379
response_tuple, response_handler = self._call_expecting_body(
2380
'Repository.get_revision_signature_text', path, revision_id)
2381
except errors.UnknownSmartMethod:
2383
return self._real_repository.get_signature_text(revision_id)
2384
except errors.NoSuchRevision, err:
2385
for fallback in self._fallback_repositories:
2387
return fallback.get_signature_text(revision_id)
2388
except errors.NoSuchRevision:
2392
if response_tuple[0] != 'ok':
2393
raise errors.UnexpectedSmartServerResponse(response_tuple)
2394
return response_handler.read_body_bytes()
2397
def _get_inventory_xml(self, revision_id):
2398
# This call is used by older working tree formats,
2399
# which stored a serialized basis inventory.
2401
return self._real_repository._get_inventory_xml(revision_id)
642
2404
def reconcile(self, other=None, thorough=False):
644
return self._real_repository.reconcile(other=other, thorough=thorough)
2405
from bzrlib.reconcile import RepoReconciler
2406
path = self.bzrdir._path_for_remote_call(self._client)
2408
response, handler = self._call_expecting_body(
2409
'Repository.reconcile', path, self._lock_token)
2410
except (errors.UnknownSmartMethod, errors.TokenLockingNotSupported):
2412
return self._real_repository.reconcile(other=other, thorough=thorough)
2413
if response != ('ok', ):
2414
raise errors.UnexpectedSmartServerResponse(response)
2415
body = handler.read_body_bytes()
2416
result = RepoReconciler(self)
2417
for line in body.split('\n'):
2420
key, val_text = line.split(':')
2421
if key == "garbage_inventories":
2422
result.garbage_inventories = int(val_text)
2423
elif key == "inconsistent_parents":
2424
result.inconsistent_parents = int(val_text)
2426
mutter("unknown reconcile key %r" % key)
646
2429
def all_revision_ids(self):
648
return self._real_repository.all_revision_ids()
651
def get_deltas_for_revisions(self, revisions):
653
return self._real_repository.get_deltas_for_revisions(revisions)
656
def get_revision_delta(self, revision_id):
658
return self._real_repository.get_revision_delta(revision_id)
2430
path = self.bzrdir._path_for_remote_call(self._client)
2432
response_tuple, response_handler = self._call_expecting_body(
2433
"Repository.all_revision_ids", path)
2434
except errors.UnknownSmartMethod:
2436
return self._real_repository.all_revision_ids()
2437
if response_tuple != ("ok", ):
2438
raise errors.UnexpectedSmartServerResponse(response_tuple)
2439
revids = set(response_handler.read_body_bytes().splitlines())
2440
for fallback in self._fallback_repositories:
2441
revids.update(set(fallback.all_revision_ids()))
2444
def _filtered_revision_trees(self, revision_ids, file_ids):
2445
"""Return Tree for a revision on this branch with only some files.
2447
:param revision_ids: a sequence of revision-ids;
2448
a revision-id may not be None or 'null:'
2449
:param file_ids: if not None, the result is filtered
2450
so that only those file-ids, their parents and their
2451
children are included.
2453
inventories = self.iter_inventories(revision_ids)
2454
for inv in inventories:
2455
# Should we introduce a FilteredRevisionTree class rather
2456
# than pre-filter the inventory here?
2457
filtered_inv = inv.filter(file_ids)
2458
yield InventoryRevisionTree(self, filtered_inv, filtered_inv.revision_id)
2461
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
2462
medium = self._client._medium
2463
if medium._is_remote_before((1, 2)):
2465
for delta in self._real_repository.get_deltas_for_revisions(
2466
revisions, specific_fileids):
2469
# Get the revision-ids of interest
2470
required_trees = set()
2471
for revision in revisions:
2472
required_trees.add(revision.revision_id)
2473
required_trees.update(revision.parent_ids[:1])
2475
# Get the matching filtered trees. Note that it's more
2476
# efficient to pass filtered trees to changes_from() rather
2477
# than doing the filtering afterwards. changes_from() could
2478
# arguably do the filtering itself but it's path-based, not
2479
# file-id based, so filtering before or afterwards is
2481
if specific_fileids is None:
2482
trees = dict((t.get_revision_id(), t) for
2483
t in self.revision_trees(required_trees))
2485
trees = dict((t.get_revision_id(), t) for
2486
t in self._filtered_revision_trees(required_trees,
2489
# Calculate the deltas
2490
for revision in revisions:
2491
if not revision.parent_ids:
2492
old_tree = self.revision_tree(_mod_revision.NULL_REVISION)
2494
old_tree = trees[revision.parent_ids[0]]
2495
yield trees[revision.revision_id].changes_from(old_tree)
2498
def get_revision_delta(self, revision_id, specific_fileids=None):
2499
r = self.get_revision(revision_id)
2500
return list(self.get_deltas_for_revisions([r],
2501
specific_fileids=specific_fileids))[0]
660
2503
@needs_read_lock
661
2504
def revision_trees(self, revision_ids):
663
return self._real_repository.revision_trees(revision_ids)
2505
inventories = self.iter_inventories(revision_ids)
2506
for inv in inventories:
2507
yield InventoryRevisionTree(self, inv, inv.revision_id)
665
2509
@needs_read_lock
666
2510
def get_revision_reconcile(self, revision_id):
668
2512
return self._real_repository.get_revision_reconcile(revision_id)
670
2514
@needs_read_lock
671
def check(self, revision_ids):
2515
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
672
2516
self._ensure_real()
673
return self._real_repository.check(revision_ids)
2517
return self._real_repository.check(revision_ids=revision_ids,
2518
callback_refs=callback_refs, check_repo=check_repo)
675
2520
def copy_content_into(self, destination, revision_id=None):
677
return self._real_repository.copy_content_into(
678
destination, revision_id=revision_id)
680
def _copy_repository_tarball(self, destination, revision_id=None):
2521
"""Make a complete copy of the content in self into destination.
2523
This is a destructive operation! Do not use it on existing
2526
interrepo = _mod_repository.InterRepository.get(self, destination)
2527
return interrepo.copy_content(revision_id)
2529
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
681
2530
# get a tarball of the remote repository, and copy from that into the
683
from bzrlib import osutils
686
from StringIO import StringIO
687
2533
# TODO: Maybe a progress bar while streaming the tarball?
688
note("Copying repository content as tarball...")
2534
note(gettext("Copying repository content as tarball..."))
689
2535
tar_file = self._get_tarball('bz2')
2536
if tar_file is None:
2538
destination = to_bzrdir.create_repository()
691
2540
tar = tarfile.open('repository', fileobj=tar_file,
693
tmpdir = tempfile.mkdtemp()
2542
tmpdir = osutils.mkdtemp()
695
2544
_extract_tar(tar, tmpdir)
696
tmp_bzrdir = BzrDir.open(tmpdir)
2545
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
697
2546
tmp_repo = tmp_bzrdir.open_repository()
698
2547
tmp_repo.copy_content_into(destination, revision_id)
700
2549
osutils.rmtree(tmpdir)
702
2551
tar_file.close()
703
# TODO: if the server doesn't support this operation, maybe do it the
704
# slow way using the _real_repository?
706
2553
# TODO: Suggestion from john: using external tar is much faster than
707
2554
# python's tarfile library, but it may not work on windows.
2557
def inventories(self):
2558
"""Decorate the real repository for now.
2560
In the long term a full blown network facility is needed to
2561
avoid creating a real repository object locally.
2564
return self._real_repository.inventories
709
2566
@needs_write_lock
2567
def pack(self, hint=None, clean_obsolete_packs=False):
711
2568
"""Compress the data within the repository.
713
This is not currently implemented within the smart server.
2573
body = "".join([l+"\n" for l in hint])
2574
path = self.bzrdir._path_for_remote_call(self._client)
2576
response, handler = self._call_with_body_bytes_expecting_body(
2577
'Repository.pack', (path, self._lock_token,
2578
str(clean_obsolete_packs)), body)
2579
except errors.UnknownSmartMethod:
2581
return self._real_repository.pack(hint=hint,
2582
clean_obsolete_packs=clean_obsolete_packs)
2583
handler.cancel_read_body()
2584
if response != ('ok', ):
2585
raise errors.UnexpectedSmartServerResponse(response)
2588
def revisions(self):
2589
"""Decorate the real repository for now.
2591
In the long term a full blown network facility is needed.
715
2593
self._ensure_real()
716
return self._real_repository.pack()
2594
return self._real_repository.revisions
718
2596
def set_make_working_trees(self, new_value):
719
raise NotImplementedError(self.set_make_working_trees)
2598
new_value_str = "True"
2600
new_value_str = "False"
2601
path = self.bzrdir._path_for_remote_call(self._client)
2603
response = self._call(
2604
'Repository.set_make_working_trees', path, new_value_str)
2605
except errors.UnknownSmartMethod:
2607
self._real_repository.set_make_working_trees(new_value)
2609
if response[0] != 'ok':
2610
raise errors.UnexpectedSmartServerResponse(response)
2613
def signatures(self):
2614
"""Decorate the real repository for now.
2616
In the long term a full blown network facility is needed to avoid
2617
creating a real repository object locally.
2620
return self._real_repository.signatures
721
2622
@needs_write_lock
722
2623
def sign_revision(self, revision_id, gpg_strategy):
2624
testament = _mod_testament.Testament.from_revision(self, revision_id)
2625
plaintext = testament.as_short_text()
2626
self.store_revision_signature(gpg_strategy, plaintext, revision_id)
2630
"""Decorate the real repository for now.
2632
In the long term a full blown network facility is needed to avoid
2633
creating a real repository object locally.
723
2635
self._ensure_real()
724
return self._real_repository.sign_revision(revision_id, gpg_strategy)
2636
return self._real_repository.texts
2638
def _iter_revisions_rpc(self, revision_ids):
2639
body = "\n".join(revision_ids)
2640
path = self.bzrdir._path_for_remote_call(self._client)
2641
response_tuple, response_handler = (
2642
self._call_with_body_bytes_expecting_body(
2643
"Repository.iter_revisions", (path, ), body))
2644
if response_tuple[0] != "ok":
2645
raise errors.UnexpectedSmartServerResponse(response_tuple)
2646
serializer_format = response_tuple[1]
2647
serializer = serializer_format_registry.get(serializer_format)
2648
byte_stream = response_handler.read_streamed_body()
2649
decompressor = zlib.decompressobj()
2651
for bytes in byte_stream:
2652
chunks.append(decompressor.decompress(bytes))
2653
if decompressor.unused_data != "":
2654
chunks.append(decompressor.flush())
2655
yield serializer.read_revision_from_string("".join(chunks))
2656
unused = decompressor.unused_data
2657
decompressor = zlib.decompressobj()
2658
chunks = [decompressor.decompress(unused)]
2659
chunks.append(decompressor.flush())
2660
text = "".join(chunks)
2662
yield serializer.read_revision_from_string("".join(chunks))
726
2664
@needs_read_lock
727
2665
def get_revisions(self, revision_ids):
729
return self._real_repository.get_revisions(revision_ids)
2666
if revision_ids is None:
2667
revision_ids = self.all_revision_ids()
2669
for rev_id in revision_ids:
2670
if not rev_id or not isinstance(rev_id, basestring):
2671
raise errors.InvalidRevisionId(
2672
revision_id=rev_id, branch=self)
2674
missing = set(revision_ids)
2676
for rev in self._iter_revisions_rpc(revision_ids):
2677
missing.remove(rev.revision_id)
2678
revs[rev.revision_id] = rev
2679
except errors.UnknownSmartMethod:
2681
return self._real_repository.get_revisions(revision_ids)
2682
for fallback in self._fallback_repositories:
2685
for revid in list(missing):
2686
# XXX JRV 2011-11-20: It would be nice if there was a
2687
# public method on Repository that could be used to query
2688
# for revision objects *without* failing completely if one
2689
# was missing. There is VersionedFileRepository._iter_revisions,
2690
# but unfortunately that's private and not provided by
2691
# all repository implementations.
2693
revs[revid] = fallback.get_revision(revid)
2694
except errors.NoSuchRevision:
2697
missing.remove(revid)
2699
raise errors.NoSuchRevision(self, list(missing)[0])
2700
return [revs[revid] for revid in revision_ids]
731
2702
def supports_rich_root(self):
733
return self._real_repository.supports_rich_root()
735
def iter_reverse_revision_history(self, revision_id):
737
return self._real_repository.iter_reverse_revision_history(revision_id)
2703
return self._format.rich_root_data
740
2706
def _serializer(self):
742
return self._real_repository._serializer
2707
return self._format._serializer
744
2710
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
746
return self._real_repository.store_revision_signature(
747
gpg_strategy, plaintext, revision_id)
2711
signature = gpg_strategy.sign(plaintext)
2712
self.add_signature_text(revision_id, signature)
2714
def add_signature_text(self, revision_id, signature):
2715
if self._real_repository:
2716
# If there is a real repository the write group will
2717
# be in the real repository as well, so use that:
2719
return self._real_repository.add_signature_text(
2720
revision_id, signature)
2721
path = self.bzrdir._path_for_remote_call(self._client)
2722
response, handler = self._call_with_body_bytes_expecting_body(
2723
'Repository.add_signature_text', (path, self._lock_token,
2724
revision_id) + tuple(self._write_group_tokens), signature)
2725
handler.cancel_read_body()
2727
if response[0] != 'ok':
2728
raise errors.UnexpectedSmartServerResponse(response)
2729
self._write_group_tokens = response[1:]
749
2731
def has_signature_for_revision_id(self, revision_id):
751
return self._real_repository.has_signature_for_revision_id(revision_id)
2732
path = self.bzrdir._path_for_remote_call(self._client)
2734
response = self._call('Repository.has_signature_for_revision_id',
2736
except errors.UnknownSmartMethod:
2738
return self._real_repository.has_signature_for_revision_id(
2740
if response[0] not in ('yes', 'no'):
2741
raise SmartProtocolError('unexpected response code %s' % (response,))
2742
if response[0] == 'yes':
2744
for fallback in self._fallback_repositories:
2745
if fallback.has_signature_for_revision_id(revision_id):
2750
def verify_revision_signature(self, revision_id, gpg_strategy):
2751
if not self.has_signature_for_revision_id(revision_id):
2752
return gpg.SIGNATURE_NOT_SIGNED, None
2753
signature = self.get_signature_text(revision_id)
2755
testament = _mod_testament.Testament.from_revision(self, revision_id)
2756
plaintext = testament.as_short_text()
2758
return gpg_strategy.verify(signature, plaintext)
2760
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2762
return self._real_repository.item_keys_introduced_by(revision_ids,
2763
_files_pb=_files_pb)
2765
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2767
return self._real_repository._find_inconsistent_revision_parents(
2770
def _check_for_inconsistent_revision_parents(self):
2772
return self._real_repository._check_for_inconsistent_revision_parents()
2774
def _make_parents_provider(self, other=None):
2775
providers = [self._unstacked_provider]
2776
if other is not None:
2777
providers.insert(0, other)
2778
return graph.StackedParentsProvider(_LazyListJoin(
2779
providers, self._fallback_repositories))
2781
def _serialise_search_recipe(self, recipe):
2782
"""Serialise a graph search recipe.
2784
:param recipe: A search recipe (start, stop, count).
2785
:return: Serialised bytes.
2787
start_keys = ' '.join(recipe[1])
2788
stop_keys = ' '.join(recipe[2])
2789
count = str(recipe[3])
2790
return '\n'.join((start_keys, stop_keys, count))
2792
def _serialise_search_result(self, search_result):
2793
parts = search_result.get_network_struct()
2794
return '\n'.join(parts)
2797
path = self.bzrdir._path_for_remote_call(self._client)
2799
response = self._call('PackRepository.autopack', path)
2800
except errors.UnknownSmartMethod:
2802
self._real_repository._pack_collection.autopack()
2805
if response[0] != 'ok':
2806
raise errors.UnexpectedSmartServerResponse(response)
2809
class RemoteStreamSink(vf_repository.StreamSink):
2811
def _insert_real(self, stream, src_format, resume_tokens):
2812
self.target_repo._ensure_real()
2813
sink = self.target_repo._real_repository._get_sink()
2814
result = sink.insert_stream(stream, src_format, resume_tokens)
2816
self.target_repo.autopack()
2819
def insert_stream(self, stream, src_format, resume_tokens):
2820
target = self.target_repo
2821
target._unstacked_provider.missing_keys.clear()
2822
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2823
if target._lock_token:
2824
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2825
lock_args = (target._lock_token or '',)
2827
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2829
client = target._client
2830
medium = client._medium
2831
path = target.bzrdir._path_for_remote_call(client)
2832
# Probe for the verb to use with an empty stream before sending the
2833
# real stream to it. We do this both to avoid the risk of sending a
2834
# large request that is then rejected, and because we don't want to
2835
# implement a way to buffer, rewind, or restart the stream.
2837
for verb, required_version in candidate_calls:
2838
if medium._is_remote_before(required_version):
2841
# We've already done the probing (and set _is_remote_before) on
2842
# a previous insert.
2845
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2847
response = client.call_with_body_stream(
2848
(verb, path, '') + lock_args, byte_stream)
2849
except errors.UnknownSmartMethod:
2850
medium._remember_remote_is_before(required_version)
2856
return self._insert_real(stream, src_format, resume_tokens)
2857
self._last_inv_record = None
2858
self._last_substream = None
2859
if required_version < (1, 19):
2860
# Remote side doesn't support inventory deltas. Wrap the stream to
2861
# make sure we don't send any. If the stream contains inventory
2862
# deltas we'll interrupt the smart insert_stream request and
2864
stream = self._stop_stream_if_inventory_delta(stream)
2865
byte_stream = smart_repo._stream_to_byte_stream(
2867
resume_tokens = ' '.join(resume_tokens)
2868
response = client.call_with_body_stream(
2869
(verb, path, resume_tokens) + lock_args, byte_stream)
2870
if response[0][0] not in ('ok', 'missing-basis'):
2871
raise errors.UnexpectedSmartServerResponse(response)
2872
if self._last_substream is not None:
2873
# The stream included an inventory-delta record, but the remote
2874
# side isn't new enough to support them. So we need to send the
2875
# rest of the stream via VFS.
2876
self.target_repo.refresh_data()
2877
return self._resume_stream_with_vfs(response, src_format)
2878
if response[0][0] == 'missing-basis':
2879
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2880
resume_tokens = tokens
2881
return resume_tokens, set(missing_keys)
2883
self.target_repo.refresh_data()
2886
def _resume_stream_with_vfs(self, response, src_format):
2887
"""Resume sending a stream via VFS, first resending the record and
2888
substream that couldn't be sent via an insert_stream verb.
2890
if response[0][0] == 'missing-basis':
2891
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2892
# Ignore missing_keys, we haven't finished inserting yet
2895
def resume_substream():
2896
# Yield the substream that was interrupted.
2897
for record in self._last_substream:
2899
self._last_substream = None
2900
def resume_stream():
2901
# Finish sending the interrupted substream
2902
yield ('inventory-deltas', resume_substream())
2903
# Then simply continue sending the rest of the stream.
2904
for substream_kind, substream in self._last_stream:
2905
yield substream_kind, substream
2906
return self._insert_real(resume_stream(), src_format, tokens)
2908
def _stop_stream_if_inventory_delta(self, stream):
2909
"""Normally this just lets the original stream pass-through unchanged.
2911
However if any 'inventory-deltas' substream occurs it will stop
2912
streaming, and store the interrupted substream and stream in
2913
self._last_substream and self._last_stream so that the stream can be
2914
resumed by _resume_stream_with_vfs.
2917
stream_iter = iter(stream)
2918
for substream_kind, substream in stream_iter:
2919
if substream_kind == 'inventory-deltas':
2920
self._last_substream = substream
2921
self._last_stream = stream_iter
2924
yield substream_kind, substream
2927
class RemoteStreamSource(vf_repository.StreamSource):
2928
"""Stream data from a remote server."""
2930
def get_stream(self, search):
2931
if (self.from_repository._fallback_repositories and
2932
self.to_format._fetch_order == 'topological'):
2933
return self._real_stream(self.from_repository, search)
2936
repos = [self.from_repository]
2942
repos.extend(repo._fallback_repositories)
2943
sources.append(repo)
2944
return self.missing_parents_chain(search, sources)
2946
def get_stream_for_missing_keys(self, missing_keys):
2947
self.from_repository._ensure_real()
2948
real_repo = self.from_repository._real_repository
2949
real_source = real_repo._get_source(self.to_format)
2950
return real_source.get_stream_for_missing_keys(missing_keys)
2952
def _real_stream(self, repo, search):
2953
"""Get a stream for search from repo.
2955
This never called RemoteStreamSource.get_stream, and is a helper
2956
for RemoteStreamSource._get_stream to allow getting a stream
2957
reliably whether fallback back because of old servers or trying
2958
to stream from a non-RemoteRepository (which the stacked support
2961
source = repo._get_source(self.to_format)
2962
if isinstance(source, RemoteStreamSource):
2964
source = repo._real_repository._get_source(self.to_format)
2965
return source.get_stream(search)
2967
def _get_stream(self, repo, search):
2968
"""Core worker to get a stream from repo for search.
2970
This is used by both get_stream and the stacking support logic. It
2971
deliberately gets a stream for repo which does not need to be
2972
self.from_repository. In the event that repo is not Remote, or
2973
cannot do a smart stream, a fallback is made to the generic
2974
repository._get_stream() interface, via self._real_stream.
2976
In the event of stacking, streams from _get_stream will not
2977
contain all the data for search - this is normal (see get_stream).
2979
:param repo: A repository.
2980
:param search: A search.
2982
# Fallbacks may be non-smart
2983
if not isinstance(repo, RemoteRepository):
2984
return self._real_stream(repo, search)
2985
client = repo._client
2986
medium = client._medium
2987
path = repo.bzrdir._path_for_remote_call(client)
2988
search_bytes = repo._serialise_search_result(search)
2989
args = (path, self.to_format.network_name())
2991
('Repository.get_stream_1.19', (1, 19)),
2992
('Repository.get_stream', (1, 13))]
2995
for verb, version in candidate_verbs:
2996
if medium._is_remote_before(version):
2999
response = repo._call_with_body_bytes_expecting_body(
3000
verb, args, search_bytes)
3001
except errors.UnknownSmartMethod:
3002
medium._remember_remote_is_before(version)
3003
except errors.UnknownErrorFromSmartServer, e:
3004
if isinstance(search, vf_search.EverythingResult):
3005
error_verb = e.error_from_smart_server.error_verb
3006
if error_verb == 'BadSearch':
3007
# Pre-2.4 servers don't support this sort of search.
3008
# XXX: perhaps falling back to VFS on BadSearch is a
3009
# good idea in general? It might provide a little bit
3010
# of protection against client-side bugs.
3011
medium._remember_remote_is_before((2, 4))
3015
response_tuple, response_handler = response
3019
return self._real_stream(repo, search)
3020
if response_tuple[0] != 'ok':
3021
raise errors.UnexpectedSmartServerResponse(response_tuple)
3022
byte_stream = response_handler.read_streamed_body()
3023
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
3024
self._record_counter)
3025
if src_format.network_name() != repo._format.network_name():
3026
raise AssertionError(
3027
"Mismatched RemoteRepository and stream src %r, %r" % (
3028
src_format.network_name(), repo._format.network_name()))
3031
def missing_parents_chain(self, search, sources):
3032
"""Chain multiple streams together to handle stacking.
3034
:param search: The overall search to satisfy with streams.
3035
:param sources: A list of Repository objects to query.
3037
self.from_serialiser = self.from_repository._format._serializer
3038
self.seen_revs = set()
3039
self.referenced_revs = set()
3040
# If there are heads in the search, or the key count is > 0, we are not
3042
while not search.is_empty() and len(sources) > 1:
3043
source = sources.pop(0)
3044
stream = self._get_stream(source, search)
3045
for kind, substream in stream:
3046
if kind != 'revisions':
3047
yield kind, substream
3049
yield kind, self.missing_parents_rev_handler(substream)
3050
search = search.refine(self.seen_revs, self.referenced_revs)
3051
self.seen_revs = set()
3052
self.referenced_revs = set()
3053
if not search.is_empty():
3054
for kind, stream in self._get_stream(sources[0], search):
3057
def missing_parents_rev_handler(self, substream):
3058
for content in substream:
3059
revision_bytes = content.get_bytes_as('fulltext')
3060
revision = self.from_serialiser.read_revision_from_string(
3062
self.seen_revs.add(content.key[-1])
3063
self.referenced_revs.update(revision.parent_ids)
754
3067
class RemoteBranchLockableFiles(LockableFiles):
755
3068
"""A 'LockableFiles' implementation that talks to a smart server.
757
3070
This is not a public interface class.
770
3083
self._dir_mode = None
771
3084
self._file_mode = None
774
"""'get' a remote path as per the LockableFiles interface.
776
:param path: the file to 'get'. If this is 'branch.conf', we do not
777
just retrieve a file, instead we ask the smart server to generate
778
a configuration for us - which is retrieved as an INI file.
780
if path == 'branch.conf':
781
path = self.bzrdir._path_for_remote_call(self._client)
782
response = self._client.call_expecting_body(
783
'Branch.get_config_file', path)
784
assert response[0][0] == 'ok', \
785
'unexpected response code %s' % (response[0],)
786
return StringIO(response[1].read_body_bytes())
789
return LockableFiles.get(self, path)
792
3087
class RemoteBranchFormat(branch.BranchFormat):
3089
def __init__(self, network_name=None):
3090
super(RemoteBranchFormat, self).__init__()
3091
self._matchingbzrdir = RemoteBzrDirFormat()
3092
self._matchingbzrdir.set_branch_format(self)
3093
self._custom_format = None
3094
self._network_name = network_name
794
3096
def __eq__(self, other):
795
return (isinstance(other, RemoteBranchFormat) and
3097
return (isinstance(other, RemoteBranchFormat) and
796
3098
self.__dict__ == other.__dict__)
3100
def _ensure_real(self):
3101
if self._custom_format is None:
3103
self._custom_format = branch.network_format_registry.get(
3106
raise errors.UnknownFormatError(kind='branch',
3107
format=self._network_name)
798
3109
def get_format_description(self):
799
return 'Remote BZR Branch'
801
def get_format_string(self):
802
return 'Remote BZR Branch'
804
def open(self, a_bzrdir):
805
assert isinstance(a_bzrdir, RemoteBzrDir)
806
return a_bzrdir.open_branch()
808
def initialize(self, a_bzrdir):
809
assert isinstance(a_bzrdir, RemoteBzrDir)
810
return a_bzrdir.create_branch()
3111
return 'Remote: ' + self._custom_format.get_format_description()
3113
def network_name(self):
3114
return self._network_name
3116
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
3117
return a_bzrdir.open_branch(name=name,
3118
ignore_fallbacks=ignore_fallbacks)
3120
def _vfs_initialize(self, a_bzrdir, name, append_revisions_only):
3121
# Initialisation when using a local bzrdir object, or a non-vfs init
3122
# method is not available on the server.
3123
# self._custom_format is always set - the start of initialize ensures
3125
if isinstance(a_bzrdir, RemoteBzrDir):
3126
a_bzrdir._ensure_real()
3127
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
3128
name=name, append_revisions_only=append_revisions_only)
3130
# We assume the bzrdir is parameterised; it may not be.
3131
result = self._custom_format.initialize(a_bzrdir, name=name,
3132
append_revisions_only=append_revisions_only)
3133
if (isinstance(a_bzrdir, RemoteBzrDir) and
3134
not isinstance(result, RemoteBranch)):
3135
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
3139
def initialize(self, a_bzrdir, name=None, repository=None,
3140
append_revisions_only=None):
3142
name = a_bzrdir._get_selected_branch()
3143
# 1) get the network name to use.
3144
if self._custom_format:
3145
network_name = self._custom_format.network_name()
3147
# Select the current bzrlib default and ask for that.
3148
reference_bzrdir_format = controldir.format_registry.get('default')()
3149
reference_format = reference_bzrdir_format.get_branch_format()
3150
self._custom_format = reference_format
3151
network_name = reference_format.network_name()
3152
# Being asked to create on a non RemoteBzrDir:
3153
if not isinstance(a_bzrdir, RemoteBzrDir):
3154
return self._vfs_initialize(a_bzrdir, name=name,
3155
append_revisions_only=append_revisions_only)
3156
medium = a_bzrdir._client._medium
3157
if medium._is_remote_before((1, 13)):
3158
return self._vfs_initialize(a_bzrdir, name=name,
3159
append_revisions_only=append_revisions_only)
3160
# Creating on a remote bzr dir.
3161
# 2) try direct creation via RPC
3162
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
3164
# XXX JRV20100304: Support creating colocated branches
3165
raise errors.NoColocatedBranchSupport(self)
3166
verb = 'BzrDir.create_branch'
3168
response = a_bzrdir._call(verb, path, network_name)
3169
except errors.UnknownSmartMethod:
3170
# Fallback - use vfs methods
3171
medium._remember_remote_is_before((1, 13))
3172
return self._vfs_initialize(a_bzrdir, name=name,
3173
append_revisions_only=append_revisions_only)
3174
if response[0] != 'ok':
3175
raise errors.UnexpectedSmartServerResponse(response)
3176
# Turn the response into a RemoteRepository object.
3177
format = RemoteBranchFormat(network_name=response[1])
3178
repo_format = response_tuple_to_repo_format(response[3:])
3179
repo_path = response[2]
3180
if repository is not None:
3181
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
3182
url_diff = urlutils.relative_url(repository.user_url,
3185
raise AssertionError(
3186
'repository.user_url %r does not match URL from server '
3187
'response (%r + %r)'
3188
% (repository.user_url, a_bzrdir.user_url, repo_path))
3189
remote_repo = repository
3192
repo_bzrdir = a_bzrdir
3194
repo_bzrdir = RemoteBzrDir(
3195
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
3197
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
3198
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
3199
format=format, setup_stacking=False, name=name)
3200
if append_revisions_only:
3201
remote_branch.set_append_revisions_only(append_revisions_only)
3202
# XXX: We know this is a new branch, so it must have revno 0, revid
3203
# NULL_REVISION. Creating the branch locked would make this be unable
3204
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
3205
remote_branch._last_revision_info_cache = 0, NULL_REVISION
3206
return remote_branch
3208
def make_tags(self, branch):
3210
return self._custom_format.make_tags(branch)
812
3212
def supports_tags(self):
813
3213
# Remote branches might support tags, but we won't know until we
814
3214
# access the real remote branch.
818
class RemoteBranch(branch.Branch):
3216
return self._custom_format.supports_tags()
3218
def supports_stacking(self):
3220
return self._custom_format.supports_stacking()
3222
def supports_set_append_revisions_only(self):
3224
return self._custom_format.supports_set_append_revisions_only()
3226
def _use_default_local_heads_to_fetch(self):
3227
# If the branch format is a metadir format *and* its heads_to_fetch
3228
# implementation is not overridden vs the base class, we can use the
3229
# base class logic rather than use the heads_to_fetch RPC. This is
3230
# usually cheaper in terms of net round trips, as the last-revision and
3231
# tags info fetched is cached and would be fetched anyway.
3233
if isinstance(self._custom_format, branch.BranchFormatMetadir):
3234
branch_class = self._custom_format._branch_class()
3235
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
3236
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
3241
class RemoteBranchStore(_mod_config.IniFileStore):
3242
"""Branch store which attempts to use HPSS calls to retrieve branch store.
3244
Note that this is specific to bzr-based formats.
3247
def __init__(self, branch):
3248
super(RemoteBranchStore, self).__init__()
3249
self.branch = branch
3251
self._real_store = None
3253
def external_url(self):
3254
return self.branch.user_url
3256
def _load_content(self):
3257
path = self.branch._remote_path()
3259
response, handler = self.branch._call_expecting_body(
3260
'Branch.get_config_file', path)
3261
except errors.UnknownSmartMethod:
3263
return self._real_store._load_content()
3264
if len(response) and response[0] != 'ok':
3265
raise errors.UnexpectedSmartServerResponse(response)
3266
return handler.read_body_bytes()
3268
def _save_content(self, content):
3269
path = self.branch._remote_path()
3271
response, handler = self.branch._call_with_body_bytes_expecting_body(
3272
'Branch.put_config_file', (path,
3273
self.branch._lock_token, self.branch._repo_lock_token),
3275
except errors.UnknownSmartMethod:
3277
return self._real_store._save_content(content)
3278
handler.cancel_read_body()
3279
if response != ('ok', ):
3280
raise errors.UnexpectedSmartServerResponse(response)
3282
def _ensure_real(self):
3283
self.branch._ensure_real()
3284
if self._real_store is None:
3285
self._real_store = _mod_config.BranchStore(self.branch)
3288
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
819
3289
"""Branch stored on a server accessed by HPSS RPC.
821
3291
At the moment most operations are mapped down to simple file operations.
824
3294
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
3295
_client=None, format=None, setup_stacking=True, name=None,
3296
possible_transports=None):
826
3297
"""Create a RemoteBranch instance.
828
3299
:param real_branch: An optional local implementation of the branch
829
3300
format, usually accessing the data via the VFS.
830
3301
:param _client: Private parameter for testing.
3302
:param format: A RemoteBranchFormat object, None to create one
3303
automatically. If supplied it should have a network_name already
3305
:param setup_stacking: If True make an RPC call to determine the
3306
stacked (or not) status of the branch. If False assume the branch
3308
:param name: Colocated branch name
832
3310
# We intentionally don't call the parent class's __init__, because it
833
3311
# will try to assign to self.tags, which is a property in this subclass.
834
3312
# And the parent's __init__ doesn't do much anyway.
835
self._revision_history_cache = None
836
3313
self.bzrdir = remote_bzrdir
837
3315
if _client is not None:
838
3316
self._client = _client
840
self._client = client._SmartClient(self.bzrdir._shared_medium)
3318
self._client = remote_bzrdir._client
841
3319
self.repository = remote_repository
842
3320
if real_branch is not None:
843
3321
self._real_branch = real_branch
895
3473
self.bzrdir, self._client)
896
3474
return self._control_files
898
def _get_checkout_format(self):
900
return self._real_branch._get_checkout_format()
902
3476
def get_physical_lock_status(self):
903
3477
"""See Branch.get_physical_lock_status()."""
904
# should be an API call to the server, as branches must be lockable.
906
return self._real_branch.get_physical_lock_status()
3479
response = self._client.call('Branch.get_physical_lock_status',
3480
self._remote_path())
3481
except errors.UnknownSmartMethod:
3483
return self._real_branch.get_physical_lock_status()
3484
if response[0] not in ('yes', 'no'):
3485
raise errors.UnexpectedSmartServerResponse(response)
3486
return (response[0] == 'yes')
3488
def get_stacked_on_url(self):
3489
"""Get the URL this branch is stacked against.
3491
:raises NotStacked: If the branch is not stacked.
3492
:raises UnstackableBranchFormat: If the branch does not support
3494
:raises UnstackableRepositoryFormat: If the repository does not support
3498
# there may not be a repository yet, so we can't use
3499
# self._translate_error, so we can't use self._call either.
3500
response = self._client.call('Branch.get_stacked_on_url',
3501
self._remote_path())
3502
except errors.ErrorFromSmartServer, err:
3503
# there may not be a repository yet, so we can't call through
3504
# its _translate_error
3505
_translate_error(err, branch=self)
3506
except errors.UnknownSmartMethod, err:
3508
return self._real_branch.get_stacked_on_url()
3509
if response[0] != 'ok':
3510
raise errors.UnexpectedSmartServerResponse(response)
3513
def set_stacked_on_url(self, url):
3514
branch.Branch.set_stacked_on_url(self, url)
3515
# We need the stacked_on_url to be visible both locally (to not query
3516
# it repeatedly) and remotely (so smart verbs can get it server side)
3517
# Without the following line,
3518
# bzrlib.tests.per_branch.test_create_clone.TestCreateClone
3519
# .test_create_clone_on_transport_stacked_hooks_get_stacked_branch
3520
# fails for remote branches -- vila 2012-01-04
3521
self.conf_store.save_changes()
3523
self._is_stacked = False
3525
self._is_stacked = True
3527
def _vfs_get_tags_bytes(self):
3529
return self._real_branch._get_tags_bytes()
3532
def _get_tags_bytes(self):
3533
if self._tags_bytes is None:
3534
self._tags_bytes = self._get_tags_bytes_via_hpss()
3535
return self._tags_bytes
3537
def _get_tags_bytes_via_hpss(self):
3538
medium = self._client._medium
3539
if medium._is_remote_before((1, 13)):
3540
return self._vfs_get_tags_bytes()
3542
response = self._call('Branch.get_tags_bytes', self._remote_path())
3543
except errors.UnknownSmartMethod:
3544
medium._remember_remote_is_before((1, 13))
3545
return self._vfs_get_tags_bytes()
3548
def _vfs_set_tags_bytes(self, bytes):
3550
return self._real_branch._set_tags_bytes(bytes)
3552
def _set_tags_bytes(self, bytes):
3553
if self.is_locked():
3554
self._tags_bytes = bytes
3555
medium = self._client._medium
3556
if medium._is_remote_before((1, 18)):
3557
self._vfs_set_tags_bytes(bytes)
3561
self._remote_path(), self._lock_token, self._repo_lock_token)
3562
response = self._call_with_body_bytes(
3563
'Branch.set_tags_bytes', args, bytes)
3564
except errors.UnknownSmartMethod:
3565
medium._remember_remote_is_before((1, 18))
3566
self._vfs_set_tags_bytes(bytes)
908
3568
def lock_read(self):
3569
"""Lock the branch for read operations.
3571
:return: A bzrlib.lock.LogicalLockResult.
3573
self.repository.lock_read()
909
3574
if not self._lock_mode:
3575
self._note_lock('r')
910
3576
self._lock_mode = 'r'
911
3577
self._lock_count = 1
912
3578
if self._real_branch is not None:
913
3579
self._real_branch.lock_read()
915
3581
self._lock_count += 1
3582
return lock.LogicalLockResult(self.unlock)
917
3584
def _remote_lock_write(self, token):
918
3585
if token is None:
919
3586
branch_token = repo_token = ''
921
3588
branch_token = token
922
repo_token = self.repository.lock_write()
3589
repo_token = self.repository.lock_write().repository_token
923
3590
self.repository.unlock()
924
path = self.bzrdir._path_for_remote_call(self._client)
925
response = self._client.call('Branch.lock_write', path, branch_token,
927
if response[0] == 'ok':
928
ok, branch_token, repo_token = response
929
return branch_token, repo_token
930
elif response[0] == 'LockContention':
931
raise errors.LockContention('(remote lock)')
932
elif response[0] == 'TokenMismatch':
933
raise errors.TokenMismatch(token, '(remote token)')
934
elif response[0] == 'UnlockableTransport':
935
raise errors.UnlockableTransport(self.bzrdir.root_transport)
936
elif response[0] == 'ReadOnlyError':
937
raise errors.ReadOnlyError(self)
3591
err_context = {'token': token}
3593
response = self._call(
3594
'Branch.lock_write', self._remote_path(), branch_token,
3595
repo_token or '', **err_context)
3596
except errors.LockContention, e:
3597
# The LockContention from the server doesn't have any
3598
# information about the lock_url. We re-raise LockContention
3599
# with valid lock_url.
3600
raise errors.LockContention('(remote lock)',
3601
self.repository.base.split('.bzr/')[0])
3602
if response[0] != 'ok':
939
3603
raise errors.UnexpectedSmartServerResponse(response)
3604
ok, branch_token, repo_token = response
3605
return branch_token, repo_token
941
3607
def lock_write(self, token=None):
942
3608
if not self._lock_mode:
3609
self._note_lock('w')
3610
# Lock the branch and repo in one remote call.
943
3611
remote_tokens = self._remote_lock_write(token)
944
3612
self._lock_token, self._repo_lock_token = remote_tokens
945
assert self._lock_token, 'Remote server did not return a token!'
946
# TODO: We really, really, really don't want to call _ensure_real
947
# here, but it's the easiest way to ensure coherency between the
948
# state of the RemoteBranch and RemoteRepository objects and the
949
# physical locks. If we don't materialise the real objects here,
950
# then getting everything in the right state later is complex, so
951
# for now we just do it the lazy way.
952
# -- Andrew Bennetts, 2007-02-22.
3613
if not self._lock_token:
3614
raise SmartProtocolError('Remote server did not return a token!')
3615
# Tell the self.repository object that it is locked.
3616
self.repository.lock_write(
3617
self._repo_lock_token, _skip_rpc=True)
954
3619
if self._real_branch is not None:
955
self._real_branch.repository.lock_write(
956
token=self._repo_lock_token)
958
self._real_branch.lock_write(token=self._lock_token)
960
self._real_branch.repository.unlock()
3620
self._real_branch.lock_write(token=self._lock_token)
961
3621
if token is not None:
962
3622
self._leave_lock = True
964
# XXX: this case seems to be unreachable; token cannot be None.
965
3624
self._leave_lock = False
966
3625
self._lock_mode = 'w'
967
3626
self._lock_count = 1
968
3627
elif self._lock_mode == 'r':
969
raise errors.ReadOnlyTransaction
3628
raise errors.ReadOnlyError(self)
971
3630
if token is not None:
972
# A token was given to lock_write, and we're relocking, so check
973
# that the given token actually matches the one we already have.
3631
# A token was given to lock_write, and we're relocking, so
3632
# check that the given token actually matches the one we
974
3634
if token != self._lock_token:
975
3635
raise errors.TokenMismatch(token, self._lock_token)
976
3636
self._lock_count += 1
977
return self._lock_token
3637
# Re-lock the repository too.
3638
self.repository.lock_write(self._repo_lock_token)
3639
return BranchWriteLockResult(self.unlock, self._lock_token or None)
979
3641
def _unlock(self, branch_token, repo_token):
980
path = self.bzrdir._path_for_remote_call(self._client)
981
response = self._client.call('Branch.unlock', path, branch_token,
3642
err_context = {'token': str((branch_token, repo_token))}
3643
response = self._call(
3644
'Branch.unlock', self._remote_path(), branch_token,
3645
repo_token or '', **err_context)
983
3646
if response == ('ok',):
985
elif response[0] == 'TokenMismatch':
986
raise errors.TokenMismatch(
987
str((branch_token, repo_token)), '(remote tokens)')
989
raise errors.UnexpectedSmartServerResponse(response)
3648
raise errors.UnexpectedSmartServerResponse(response)
3650
@only_raises(errors.LockNotHeld, errors.LockBroken)
991
3651
def unlock(self):
992
self._lock_count -= 1
993
if not self._lock_count:
994
self._clear_cached_state()
995
mode = self._lock_mode
996
self._lock_mode = None
997
if self._real_branch is not None:
3653
self._lock_count -= 1
3654
if not self._lock_count:
3655
if self.conf_store is not None:
3656
self.conf_store.save_changes()
3657
self._clear_cached_state()
3658
mode = self._lock_mode
3659
self._lock_mode = None
3660
if self._real_branch is not None:
3661
if (not self._leave_lock and mode == 'w' and
3662
self._repo_lock_token):
3663
# If this RemoteBranch will remove the physical lock
3664
# for the repository, make sure the _real_branch
3665
# doesn't do it first. (Because the _real_branch's
3666
# repository is set to be the RemoteRepository.)
3667
self._real_branch.repository.leave_lock_in_place()
3668
self._real_branch.unlock()
3670
# Only write-locked branched need to make a remote method
3671
# call to perform the unlock.
3673
if not self._lock_token:
3674
raise AssertionError('Locked, but no token!')
3675
branch_token = self._lock_token
3676
repo_token = self._repo_lock_token
3677
self._lock_token = None
3678
self._repo_lock_token = None
998
3679
if not self._leave_lock:
999
# If this RemoteBranch will remove the physical lock for the
1000
# repository, make sure the _real_branch doesn't do it
1001
# first. (Because the _real_branch's repository is set to
1002
# be the RemoteRepository.)
1003
self._real_branch.repository.leave_lock_in_place()
1004
self._real_branch.unlock()
1006
# Only write-locked branched need to make a remote method call
1007
# to perfom the unlock.
1009
assert self._lock_token, 'Locked, but no token!'
1010
branch_token = self._lock_token
1011
repo_token = self._repo_lock_token
1012
self._lock_token = None
1013
self._repo_lock_token = None
1014
if not self._leave_lock:
1015
self._unlock(branch_token, repo_token)
3680
self._unlock(branch_token, repo_token)
3682
self.repository.unlock()
1017
3684
def break_lock(self):
1019
return self._real_branch.break_lock()
3686
response = self._call(
3687
'Branch.break_lock', self._remote_path())
3688
except errors.UnknownSmartMethod:
3690
return self._real_branch.break_lock()
3691
if response != ('ok',):
3692
raise errors.UnexpectedSmartServerResponse(response)
1021
3694
def leave_lock_in_place(self):
3695
if not self._lock_token:
3696
raise NotImplementedError(self.leave_lock_in_place)
1022
3697
self._leave_lock = True
1024
3699
def dont_leave_lock_in_place(self):
3700
if not self._lock_token:
3701
raise NotImplementedError(self.dont_leave_lock_in_place)
1025
3702
self._leave_lock = False
1027
def last_revision_info(self):
1028
"""See Branch.last_revision_info()."""
1029
path = self.bzrdir._path_for_remote_call(self._client)
1030
response = self._client.call('Branch.last_revision_info', path)
1031
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
3705
def get_rev_id(self, revno, history=None):
3707
return _mod_revision.NULL_REVISION
3708
last_revision_info = self.last_revision_info()
3709
ok, result = self.repository.get_rev_id_for_revno(
3710
revno, last_revision_info)
3713
missing_parent = result[1]
3714
# Either the revision named by the server is missing, or its parent
3715
# is. Call get_parent_map to determine which, so that we report a
3717
parent_map = self.repository.get_parent_map([missing_parent])
3718
if missing_parent in parent_map:
3719
missing_parent = parent_map[missing_parent]
3720
raise errors.RevisionNotPresent(missing_parent, self.repository)
3722
def _read_last_revision_info(self):
3723
response = self._call('Branch.last_revision_info', self._remote_path())
3724
if response[0] != 'ok':
3725
raise SmartProtocolError('unexpected response code %s' % (response,))
1032
3726
revno = int(response[1])
1033
3727
last_revision = response[2]
1034
3728
return (revno, last_revision)
1036
3730
def _gen_revision_history(self):
1037
3731
"""See Branch._gen_revision_history()."""
1038
path = self.bzrdir._path_for_remote_call(self._client)
1039
response = self._client.call_expecting_body(
1040
'Branch.revision_history', path)
1041
assert response[0][0] == 'ok', ('unexpected response code %s'
1043
result = response[1].read_body_bytes().split('\x00')
3732
if self._is_stacked:
3734
return self._real_branch._gen_revision_history()
3735
response_tuple, response_handler = self._call_expecting_body(
3736
'Branch.revision_history', self._remote_path())
3737
if response_tuple[0] != 'ok':
3738
raise errors.UnexpectedSmartServerResponse(response_tuple)
3739
result = response_handler.read_body_bytes().split('\x00')
1044
3740
if result == ['']:
1049
def set_revision_history(self, rev_history):
1050
# Send just the tip revision of the history; the server will generate
1051
# the full history from that. If the revision doesn't exist in this
1052
# branch, NoSuchRevision will be raised.
1053
path = self.bzrdir._path_for_remote_call(self._client)
1054
if rev_history == []:
1057
rev_id = rev_history[-1]
1058
self._clear_cached_state()
1059
response = self._client.call('Branch.set_last_revision',
1060
path, self._lock_token, self._repo_lock_token, rev_id)
1061
if response[0] == 'NoSuchRevision':
1062
raise NoSuchRevision(self, rev_id)
1064
assert response == ('ok',), (
1065
'unexpected response code %r' % (response,))
1066
self._cache_revision_history(rev_history)
1068
def get_parent(self):
1070
return self._real_branch.get_parent()
1072
def set_parent(self, url):
1074
return self._real_branch.set_parent(url)
1076
def get_config(self):
1077
return RemoteBranchConfig(self)
1079
def sprout(self, to_bzrdir, revision_id=None):
1080
# Like Branch.sprout, except that it sprouts a branch in the default
1081
# format, because RemoteBranches can't be created at arbitrary URLs.
1082
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
1083
# to_bzrdir.create_branch...
1084
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
1085
self.copy_content_into(result, revision_id=revision_id)
1086
result.set_parent(self.bzrdir.root_transport.base)
3744
def _remote_path(self):
3745
return self.bzrdir._path_for_remote_call(self._client)
3747
def _set_last_revision_descendant(self, revision_id, other_branch,
3748
allow_diverged=False, allow_overwrite_descendant=False):
3749
# This performs additional work to meet the hook contract; while its
3750
# undesirable, we have to synthesise the revno to call the hook, and
3751
# not calling the hook is worse as it means changes can't be prevented.
3752
# Having calculated this though, we can't just call into
3753
# set_last_revision_info as a simple call, because there is a set_rh
3754
# hook that some folk may still be using.
3755
old_revno, old_revid = self.last_revision_info()
3756
history = self._lefthand_history(revision_id)
3757
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3758
err_context = {'other_branch': other_branch}
3759
response = self._call('Branch.set_last_revision_ex',
3760
self._remote_path(), self._lock_token, self._repo_lock_token,
3761
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
3763
self._clear_cached_state()
3764
if len(response) != 3 and response[0] != 'ok':
3765
raise errors.UnexpectedSmartServerResponse(response)
3766
new_revno, new_revision_id = response[1:]
3767
self._last_revision_info_cache = new_revno, new_revision_id
3768
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3769
if self._real_branch is not None:
3770
cache = new_revno, new_revision_id
3771
self._real_branch._last_revision_info_cache = cache
3773
def _set_last_revision(self, revision_id):
3774
old_revno, old_revid = self.last_revision_info()
3775
# This performs additional work to meet the hook contract; while its
3776
# undesirable, we have to synthesise the revno to call the hook, and
3777
# not calling the hook is worse as it means changes can't be prevented.
3778
# Having calculated this though, we can't just call into
3779
# set_last_revision_info as a simple call, because there is a set_rh
3780
# hook that some folk may still be using.
3781
history = self._lefthand_history(revision_id)
3782
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
3783
self._clear_cached_state()
3784
response = self._call('Branch.set_last_revision',
3785
self._remote_path(), self._lock_token, self._repo_lock_token,
3787
if response != ('ok',):
3788
raise errors.UnexpectedSmartServerResponse(response)
3789
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3791
def _get_parent_location(self):
3792
medium = self._client._medium
3793
if medium._is_remote_before((1, 13)):
3794
return self._vfs_get_parent_location()
3796
response = self._call('Branch.get_parent', self._remote_path())
3797
except errors.UnknownSmartMethod:
3798
medium._remember_remote_is_before((1, 13))
3799
return self._vfs_get_parent_location()
3800
if len(response) != 1:
3801
raise errors.UnexpectedSmartServerResponse(response)
3802
parent_location = response[0]
3803
if parent_location == '':
3805
return parent_location
3807
def _vfs_get_parent_location(self):
3809
return self._real_branch._get_parent_location()
3811
def _set_parent_location(self, url):
3812
medium = self._client._medium
3813
if medium._is_remote_before((1, 15)):
3814
return self._vfs_set_parent_location(url)
3816
call_url = url or ''
3817
if type(call_url) is not str:
3818
raise AssertionError('url must be a str or None (%s)' % url)
3819
response = self._call('Branch.set_parent_location',
3820
self._remote_path(), self._lock_token, self._repo_lock_token,
3822
except errors.UnknownSmartMethod:
3823
medium._remember_remote_is_before((1, 15))
3824
return self._vfs_set_parent_location(url)
3826
raise errors.UnexpectedSmartServerResponse(response)
3828
def _vfs_set_parent_location(self, url):
3830
return self._real_branch._set_parent_location(url)
1089
3832
@needs_write_lock
1090
3833
def pull(self, source, overwrite=False, stop_revision=None,
1092
# FIXME: This asks the real branch to run the hooks, which means
1093
# they're called with the wrong target branch parameter.
1094
# The test suite specifically allows this at present but it should be
1095
# fixed. It should get a _override_hook_target branch,
1096
# as push does. -- mbp 20070405
3835
self._clear_cached_state_of_remote_branch_only()
1097
3836
self._ensure_real()
1098
self._real_branch.pull(
3837
return self._real_branch.pull(
1099
3838
source, overwrite=overwrite, stop_revision=stop_revision,
3839
_override_hook_target=self, **kwargs)
1102
3841
@needs_read_lock
1103
def push(self, target, overwrite=False, stop_revision=None):
3842
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
1104
3843
self._ensure_real()
1105
3844
return self._real_branch.push(
1106
target, overwrite=overwrite, stop_revision=stop_revision,
3845
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
1107
3846
_override_hook_source_branch=self)
1109
3848
def is_locked(self):
1110
3849
return self._lock_count >= 1
3852
def revision_id_to_dotted_revno(self, revision_id):
3853
"""Given a revision id, return its dotted revno.
3855
:return: a tuple like (1,) or (400,1,3).
3858
response = self._call('Branch.revision_id_to_revno',
3859
self._remote_path(), revision_id)
3860
except errors.UnknownSmartMethod:
3862
return self._real_branch.revision_id_to_dotted_revno(revision_id)
3863
if response[0] == 'ok':
3864
return tuple([int(x) for x in response[1:]])
3866
raise errors.UnexpectedSmartServerResponse(response)
3869
def revision_id_to_revno(self, revision_id):
3870
"""Given a revision id on the branch mainline, return its revno.
3875
response = self._call('Branch.revision_id_to_revno',
3876
self._remote_path(), revision_id)
3877
except errors.UnknownSmartMethod:
3879
return self._real_branch.revision_id_to_revno(revision_id)
3880
if response[0] == 'ok':
3881
if len(response) == 2:
3882
return int(response[1])
3883
raise NoSuchRevision(self, revision_id)
3885
raise errors.UnexpectedSmartServerResponse(response)
1112
3888
def set_last_revision_info(self, revno, revision_id):
1114
self._clear_cached_state()
1115
return self._real_branch.set_last_revision_info(revno, revision_id)
3889
# XXX: These should be returned by the set_last_revision_info verb
3890
old_revno, old_revid = self.last_revision_info()
3891
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3892
if not revision_id or not isinstance(revision_id, basestring):
3893
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3895
response = self._call('Branch.set_last_revision_info',
3896
self._remote_path(), self._lock_token, self._repo_lock_token,
3897
str(revno), revision_id)
3898
except errors.UnknownSmartMethod:
3900
self._clear_cached_state_of_remote_branch_only()
3901
self._real_branch.set_last_revision_info(revno, revision_id)
3902
self._last_revision_info_cache = revno, revision_id
3904
if response == ('ok',):
3905
self._clear_cached_state()
3906
self._last_revision_info_cache = revno, revision_id
3907
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3908
# Update the _real_branch's cache too.
3909
if self._real_branch is not None:
3910
cache = self._last_revision_info_cache
3911
self._real_branch._last_revision_info_cache = cache
3913
raise errors.UnexpectedSmartServerResponse(response)
1117
3916
def generate_revision_history(self, revision_id, last_rev=None,
1118
3917
other_branch=None):
1120
return self._real_branch.generate_revision_history(
1121
revision_id, last_rev=last_rev, other_branch=other_branch)
1126
return self._real_branch.tags
3918
medium = self._client._medium
3919
if not medium._is_remote_before((1, 6)):
3920
# Use a smart method for 1.6 and above servers
3922
self._set_last_revision_descendant(revision_id, other_branch,
3923
allow_diverged=True, allow_overwrite_descendant=True)
3925
except errors.UnknownSmartMethod:
3926
medium._remember_remote_is_before((1, 6))
3927
self._clear_cached_state_of_remote_branch_only()
3928
graph = self.repository.get_graph()
3929
(last_revno, last_revid) = self.last_revision_info()
3930
known_revision_ids = [
3931
(last_revid, last_revno),
3932
(_mod_revision.NULL_REVISION, 0),
3934
if last_rev is not None:
3935
if not graph.is_ancestor(last_rev, revision_id):
3936
# our previous tip is not merged into stop_revision
3937
raise errors.DivergedBranches(self, other_branch)
3938
revno = graph.find_distance_to_null(revision_id, known_revision_ids)
3939
self.set_last_revision_info(revno, revision_id)
1128
3941
def set_push_location(self, location):
1130
return self._real_branch.set_push_location(location)
1132
def update_revisions(self, other, stop_revision=None):
1134
return self._real_branch.update_revisions(
1135
other, stop_revision=stop_revision)
1138
class RemoteBranchConfig(BranchConfig):
1141
self.branch._ensure_real()
1142
return self.branch._real_branch.get_config().username()
1144
def _get_branch_data_config(self):
1145
self.branch._ensure_real()
1146
if self._branch_data_config is None:
1147
self._branch_data_config = TreeConfig(self.branch._real_branch)
1148
return self._branch_data_config
3942
self._set_config_location('push_location', location)
3944
def heads_to_fetch(self):
3945
if self._format._use_default_local_heads_to_fetch():
3946
# We recognise this format, and its heads-to-fetch implementation
3947
# is the default one (tip + tags). In this case it's cheaper to
3948
# just use the default implementation rather than a special RPC as
3949
# the tip and tags data is cached.
3950
return branch.Branch.heads_to_fetch(self)
3951
medium = self._client._medium
3952
if medium._is_remote_before((2, 4)):
3953
return self._vfs_heads_to_fetch()
3955
return self._rpc_heads_to_fetch()
3956
except errors.UnknownSmartMethod:
3957
medium._remember_remote_is_before((2, 4))
3958
return self._vfs_heads_to_fetch()
3960
def _rpc_heads_to_fetch(self):
3961
response = self._call('Branch.heads_to_fetch', self._remote_path())
3962
if len(response) != 2:
3963
raise errors.UnexpectedSmartServerResponse(response)
3964
must_fetch, if_present_fetch = response
3965
return set(must_fetch), set(if_present_fetch)
3967
def _vfs_heads_to_fetch(self):
3969
return self._real_branch.heads_to_fetch()
3972
class RemoteConfig(object):
3973
"""A Config that reads and writes from smart verbs.
3975
It is a low-level object that considers config data to be name/value pairs
3976
that may be associated with a section. Assigning meaning to the these
3977
values is done at higher levels like bzrlib.config.TreeConfig.
3980
def get_option(self, name, section=None, default=None):
3981
"""Return the value associated with a named option.
3983
:param name: The name of the value
3984
:param section: The section the option is in (if any)
3985
:param default: The value to return if the value is not set
3986
:return: The value or default value
3989
configobj = self._get_configobj()
3992
section_obj = configobj
3995
section_obj = configobj[section]
3998
if section_obj is None:
4001
value = section_obj.get(name, default)
4002
except errors.UnknownSmartMethod:
4003
value = self._vfs_get_option(name, section, default)
4004
for hook in _mod_config.OldConfigHooks['get']:
4005
hook(self, name, value)
4008
def _response_to_configobj(self, response):
4009
if len(response[0]) and response[0][0] != 'ok':
4010
raise errors.UnexpectedSmartServerResponse(response)
4011
lines = response[1].read_body_bytes().splitlines()
4012
conf = _mod_config.ConfigObj(lines, encoding='utf-8')
4013
for hook in _mod_config.OldConfigHooks['load']:
4018
class RemoteBranchConfig(RemoteConfig):
4019
"""A RemoteConfig for Branches."""
4021
def __init__(self, branch):
4022
self._branch = branch
4024
def _get_configobj(self):
4025
path = self._branch._remote_path()
4026
response = self._branch._client.call_expecting_body(
4027
'Branch.get_config_file', path)
4028
return self._response_to_configobj(response)
4030
def set_option(self, value, name, section=None):
4031
"""Set the value associated with a named option.
4033
:param value: The value to set
4034
:param name: The name of the value to set
4035
:param section: The section the option is in (if any)
4037
medium = self._branch._client._medium
4038
if medium._is_remote_before((1, 14)):
4039
return self._vfs_set_option(value, name, section)
4040
if isinstance(value, dict):
4041
if medium._is_remote_before((2, 2)):
4042
return self._vfs_set_option(value, name, section)
4043
return self._set_config_option_dict(value, name, section)
4045
return self._set_config_option(value, name, section)
4047
def _set_config_option(self, value, name, section):
4049
path = self._branch._remote_path()
4050
response = self._branch._client.call('Branch.set_config_option',
4051
path, self._branch._lock_token, self._branch._repo_lock_token,
4052
value.encode('utf8'), name, section or '')
4053
except errors.UnknownSmartMethod:
4054
medium = self._branch._client._medium
4055
medium._remember_remote_is_before((1, 14))
4056
return self._vfs_set_option(value, name, section)
4058
raise errors.UnexpectedSmartServerResponse(response)
4060
def _serialize_option_dict(self, option_dict):
4062
for key, value in option_dict.items():
4063
if isinstance(key, unicode):
4064
key = key.encode('utf8')
4065
if isinstance(value, unicode):
4066
value = value.encode('utf8')
4067
utf8_dict[key] = value
4068
return bencode.bencode(utf8_dict)
4070
def _set_config_option_dict(self, value, name, section):
4072
path = self._branch._remote_path()
4073
serialised_dict = self._serialize_option_dict(value)
4074
response = self._branch._client.call(
4075
'Branch.set_config_option_dict',
4076
path, self._branch._lock_token, self._branch._repo_lock_token,
4077
serialised_dict, name, section or '')
4078
except errors.UnknownSmartMethod:
4079
medium = self._branch._client._medium
4080
medium._remember_remote_is_before((2, 2))
4081
return self._vfs_set_option(value, name, section)
4083
raise errors.UnexpectedSmartServerResponse(response)
4085
def _real_object(self):
4086
self._branch._ensure_real()
4087
return self._branch._real_branch
4089
def _vfs_set_option(self, value, name, section=None):
4090
return self._real_object()._get_config().set_option(
4091
value, name, section)
4094
class RemoteBzrDirConfig(RemoteConfig):
4095
"""A RemoteConfig for BzrDirs."""
4097
def __init__(self, bzrdir):
4098
self._bzrdir = bzrdir
4100
def _get_configobj(self):
4101
medium = self._bzrdir._client._medium
4102
verb = 'BzrDir.get_config_file'
4103
if medium._is_remote_before((1, 15)):
4104
raise errors.UnknownSmartMethod(verb)
4105
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
4106
response = self._bzrdir._call_expecting_body(
4108
return self._response_to_configobj(response)
4110
def _vfs_get_option(self, name, section, default):
4111
return self._real_object()._get_config().get_option(
4112
name, section, default)
4114
def set_option(self, value, name, section=None):
4115
"""Set the value associated with a named option.
4117
:param value: The value to set
4118
:param name: The name of the value to set
4119
:param section: The section the option is in (if any)
4121
return self._real_object()._get_config().set_option(
4122
value, name, section)
4124
def _real_object(self):
4125
self._bzrdir._ensure_real()
4126
return self._bzrdir._real_bzrdir
1151
4129
def _extract_tar(tar, to_dir):
1156
4134
for tarinfo in tar:
1157
4135
tar.extract(tarinfo, to_dir)
4138
error_translators = registry.Registry()
4139
no_context_error_translators = registry.Registry()
4142
def _translate_error(err, **context):
4143
"""Translate an ErrorFromSmartServer into a more useful error.
4145
Possible context keys:
4153
If the error from the server doesn't match a known pattern, then
4154
UnknownErrorFromSmartServer is raised.
4158
return context[name]
4159
except KeyError, key_err:
4160
mutter('Missing key %r in context %r', key_err.args[0], context)
4163
"""Get the path from the context if present, otherwise use first error
4167
return context['path']
4168
except KeyError, key_err:
4170
return err.error_args[0]
4171
except IndexError, idx_err:
4173
'Missing key %r in context %r', key_err.args[0], context)
4177
translator = error_translators.get(err.error_verb)
4181
raise translator(err, find, get_path)
4183
translator = no_context_error_translators.get(err.error_verb)
4185
raise errors.UnknownErrorFromSmartServer(err)
4187
raise translator(err)
4190
error_translators.register('NoSuchRevision',
4191
lambda err, find, get_path: NoSuchRevision(
4192
find('branch'), err.error_args[0]))
4193
error_translators.register('nosuchrevision',
4194
lambda err, find, get_path: NoSuchRevision(
4195
find('repository'), err.error_args[0]))
4197
def _translate_nobranch_error(err, find, get_path):
4198
if len(err.error_args) >= 1:
4199
extra = err.error_args[0]
4202
return errors.NotBranchError(path=find('bzrdir').root_transport.base,
4205
error_translators.register('nobranch', _translate_nobranch_error)
4206
error_translators.register('norepository',
4207
lambda err, find, get_path: errors.NoRepositoryPresent(
4209
error_translators.register('UnlockableTransport',
4210
lambda err, find, get_path: errors.UnlockableTransport(
4211
find('bzrdir').root_transport))
4212
error_translators.register('TokenMismatch',
4213
lambda err, find, get_path: errors.TokenMismatch(
4214
find('token'), '(remote token)'))
4215
error_translators.register('Diverged',
4216
lambda err, find, get_path: errors.DivergedBranches(
4217
find('branch'), find('other_branch')))
4218
error_translators.register('NotStacked',
4219
lambda err, find, get_path: errors.NotStacked(branch=find('branch')))
4221
def _translate_PermissionDenied(err, find, get_path):
4223
if len(err.error_args) >= 2:
4224
extra = err.error_args[1]
4227
return errors.PermissionDenied(path, extra=extra)
4229
error_translators.register('PermissionDenied', _translate_PermissionDenied)
4230
error_translators.register('ReadError',
4231
lambda err, find, get_path: errors.ReadError(get_path()))
4232
error_translators.register('NoSuchFile',
4233
lambda err, find, get_path: errors.NoSuchFile(get_path()))
4234
error_translators.register('TokenLockingNotSupported',
4235
lambda err, find, get_path: errors.TokenLockingNotSupported(
4236
find('repository')))
4237
error_translators.register('UnsuspendableWriteGroup',
4238
lambda err, find, get_path: errors.UnsuspendableWriteGroup(
4239
repository=find('repository')))
4240
error_translators.register('UnresumableWriteGroup',
4241
lambda err, find, get_path: errors.UnresumableWriteGroup(
4242
repository=find('repository'), write_groups=err.error_args[0],
4243
reason=err.error_args[1]))
4244
no_context_error_translators.register('IncompatibleRepositories',
4245
lambda err: errors.IncompatibleRepositories(
4246
err.error_args[0], err.error_args[1], err.error_args[2]))
4247
no_context_error_translators.register('LockContention',
4248
lambda err: errors.LockContention('(remote lock)'))
4249
no_context_error_translators.register('LockFailed',
4250
lambda err: errors.LockFailed(err.error_args[0], err.error_args[1]))
4251
no_context_error_translators.register('TipChangeRejected',
4252
lambda err: errors.TipChangeRejected(err.error_args[0].decode('utf8')))
4253
no_context_error_translators.register('UnstackableBranchFormat',
4254
lambda err: errors.UnstackableBranchFormat(*err.error_args))
4255
no_context_error_translators.register('UnstackableRepositoryFormat',
4256
lambda err: errors.UnstackableRepositoryFormat(*err.error_args))
4257
no_context_error_translators.register('FileExists',
4258
lambda err: errors.FileExists(err.error_args[0]))
4259
no_context_error_translators.register('DirectoryNotEmpty',
4260
lambda err: errors.DirectoryNotEmpty(err.error_args[0]))
4262
def _translate_short_readv_error(err):
4263
args = err.error_args
4264
return errors.ShortReadvError(args[0], int(args[1]), int(args[2]),
4267
no_context_error_translators.register('ShortReadvError',
4268
_translate_short_readv_error)
4270
def _translate_unicode_error(err):
4271
encoding = str(err.error_args[0]) # encoding must always be a string
4272
val = err.error_args[1]
4273
start = int(err.error_args[2])
4274
end = int(err.error_args[3])
4275
reason = str(err.error_args[4]) # reason must always be a string
4276
if val.startswith('u:'):
4277
val = val[2:].decode('utf-8')
4278
elif val.startswith('s:'):
4279
val = val[2:].decode('base64')
4280
if err.error_verb == 'UnicodeDecodeError':
4281
raise UnicodeDecodeError(encoding, val, start, end, reason)
4282
elif err.error_verb == 'UnicodeEncodeError':
4283
raise UnicodeEncodeError(encoding, val, start, end, reason)
4285
no_context_error_translators.register('UnicodeEncodeError',
4286
_translate_unicode_error)
4287
no_context_error_translators.register('UnicodeDecodeError',
4288
_translate_unicode_error)
4289
no_context_error_translators.register('ReadOnlyError',
4290
lambda err: errors.TransportNotPossible('readonly transport'))
4291
no_context_error_translators.register('MemoryError',
4292
lambda err: errors.BzrError("remote server out of memory\n"
4293
"Retry non-remotely, or contact the server admin for details."))
4294
no_context_error_translators.register('RevisionNotPresent',
4295
lambda err: errors.RevisionNotPresent(err.error_args[0], err.error_args[1]))
4297
no_context_error_translators.register('BzrCheckError',
4298
lambda err: errors.BzrCheckError(msg=err.error_args[0]))