13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22
bzrdir as _mod_bzrdir,
30
repository as _mod_repository,
31
revision as _mod_revision,
37
from bzrlib.branch import BranchReferenceFormat, BranchWriteLockResult
38
from bzrlib.decorators import needs_read_lock, needs_write_lock, only_raises
39
from bzrlib.errors import (
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
# TODO: At some point, handle upgrades by just passing the whole request
18
# across to run on the server.
20
from cStringIO import StringIO
22
from bzrlib import branch, errors, lockdir, repository
23
from bzrlib.branch import Branch, BranchReferenceFormat
24
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
25
from bzrlib.config import BranchConfig, TreeConfig
26
from bzrlib.decorators import needs_read_lock, needs_write_lock
27
from bzrlib.errors import NoSuchRevision
43
28
from bzrlib.lockable_files import LockableFiles
44
from bzrlib.smart import client, vfs, repository as smart_repo
45
from bzrlib.smart.client import _SmartClient
46
29
from bzrlib.revision import NULL_REVISION
47
from bzrlib.repository import RepositoryWriteLockResult, _LazyListJoin
48
from bzrlib.trace import mutter, note, warning
51
class _RpcHelper(object):
52
"""Mixin class that helps with issuing RPCs."""
54
def _call(self, method, *args, **err_context):
56
return self._client.call(method, *args)
57
except errors.ErrorFromSmartServer, err:
58
self._translate_error(err, **err_context)
60
def _call_expecting_body(self, method, *args, **err_context):
62
return self._client.call_expecting_body(method, *args)
63
except errors.ErrorFromSmartServer, err:
64
self._translate_error(err, **err_context)
66
def _call_with_body_bytes(self, method, args, body_bytes, **err_context):
68
return self._client.call_with_body_bytes(method, args, body_bytes)
69
except errors.ErrorFromSmartServer, err:
70
self._translate_error(err, **err_context)
72
def _call_with_body_bytes_expecting_body(self, method, args, body_bytes,
75
return self._client.call_with_body_bytes_expecting_body(
76
method, args, body_bytes)
77
except errors.ErrorFromSmartServer, err:
78
self._translate_error(err, **err_context)
81
def response_tuple_to_repo_format(response):
82
"""Convert a response tuple describing a repository format to a format."""
83
format = RemoteRepositoryFormat()
84
format._rich_root_data = (response[0] == 'yes')
85
format._supports_tree_reference = (response[1] == 'yes')
86
format._supports_external_lookups = (response[2] == 'yes')
87
format._network_name = response[3]
91
# Note that RemoteBzrDirProber lives in bzrlib.bzrdir so bzrlib.remote
92
# does not have to be imported unless a remote format is involved.
94
class RemoteBzrDirFormat(_mod_bzrdir.BzrDirMetaFormat1):
95
"""Format representing bzrdirs accessed via a smart server"""
97
supports_workingtrees = False
100
_mod_bzrdir.BzrDirMetaFormat1.__init__(self)
101
# XXX: It's a bit ugly that the network name is here, because we'd
102
# like to believe that format objects are stateless or at least
103
# immutable, However, we do at least avoid mutating the name after
104
# it's returned. See <https://bugs.launchpad.net/bzr/+bug/504102>
105
self._network_name = None
108
return "%s(_network_name=%r)" % (self.__class__.__name__,
111
def get_format_description(self):
112
if self._network_name:
113
real_format = controldir.network_format_registry.get(self._network_name)
114
return 'Remote: ' + real_format.get_format_description()
115
return 'bzr remote bzrdir'
117
def get_format_string(self):
118
raise NotImplementedError(self.get_format_string)
120
def network_name(self):
121
if self._network_name:
122
return self._network_name
124
raise AssertionError("No network name set.")
126
def initialize_on_transport(self, transport):
128
# hand off the request to the smart server
129
client_medium = transport.get_smart_medium()
130
except errors.NoSmartMedium:
131
# TODO: lookup the local format from a server hint.
132
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
133
return local_dir_format.initialize_on_transport(transport)
134
client = _SmartClient(client_medium)
135
path = client.remote_path_from_transport(transport)
137
response = client.call('BzrDirFormat.initialize', path)
138
except errors.ErrorFromSmartServer, err:
139
_translate_error(err, path=path)
140
if response[0] != 'ok':
141
raise errors.SmartProtocolError('unexpected response code %s' % (response,))
142
format = RemoteBzrDirFormat()
143
self._supply_sub_formats_to(format)
144
return RemoteBzrDir(transport, format)
146
def parse_NoneTrueFalse(self, arg):
153
raise AssertionError("invalid arg %r" % arg)
155
def _serialize_NoneTrueFalse(self, arg):
162
def _serialize_NoneString(self, arg):
165
def initialize_on_transport_ex(self, transport, use_existing_dir=False,
166
create_prefix=False, force_new_repo=False, stacked_on=None,
167
stack_on_pwd=None, repo_format_name=None, make_working_trees=None,
170
# hand off the request to the smart server
171
client_medium = transport.get_smart_medium()
172
except errors.NoSmartMedium:
175
# Decline to open it if the server doesn't support our required
176
# version (3) so that the VFS-based transport will do it.
177
if client_medium.should_probe():
179
server_version = client_medium.protocol_version()
180
if server_version != '2':
184
except errors.SmartProtocolError:
185
# Apparently there's no usable smart server there, even though
186
# the medium supports the smart protocol.
191
client = _SmartClient(client_medium)
192
path = client.remote_path_from_transport(transport)
193
if client_medium._is_remote_before((1, 16)):
196
# TODO: lookup the local format from a server hint.
197
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
198
self._supply_sub_formats_to(local_dir_format)
199
return local_dir_format.initialize_on_transport_ex(transport,
200
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
201
force_new_repo=force_new_repo, stacked_on=stacked_on,
202
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
203
make_working_trees=make_working_trees, shared_repo=shared_repo,
205
return self._initialize_on_transport_ex_rpc(client, path, transport,
206
use_existing_dir, create_prefix, force_new_repo, stacked_on,
207
stack_on_pwd, repo_format_name, make_working_trees, shared_repo)
209
def _initialize_on_transport_ex_rpc(self, client, path, transport,
210
use_existing_dir, create_prefix, force_new_repo, stacked_on,
211
stack_on_pwd, repo_format_name, make_working_trees, shared_repo):
213
args.append(self._serialize_NoneTrueFalse(use_existing_dir))
214
args.append(self._serialize_NoneTrueFalse(create_prefix))
215
args.append(self._serialize_NoneTrueFalse(force_new_repo))
216
args.append(self._serialize_NoneString(stacked_on))
217
# stack_on_pwd is often/usually our transport
220
stack_on_pwd = transport.relpath(stack_on_pwd)
223
except errors.PathNotChild:
225
args.append(self._serialize_NoneString(stack_on_pwd))
226
args.append(self._serialize_NoneString(repo_format_name))
227
args.append(self._serialize_NoneTrueFalse(make_working_trees))
228
args.append(self._serialize_NoneTrueFalse(shared_repo))
229
request_network_name = self._network_name or \
230
_mod_bzrdir.BzrDirFormat.get_default_format().network_name()
232
response = client.call('BzrDirFormat.initialize_ex_1.16',
233
request_network_name, path, *args)
234
except errors.UnknownSmartMethod:
235
client._medium._remember_remote_is_before((1,16))
236
local_dir_format = _mod_bzrdir.BzrDirMetaFormat1()
237
self._supply_sub_formats_to(local_dir_format)
238
return local_dir_format.initialize_on_transport_ex(transport,
239
use_existing_dir=use_existing_dir, create_prefix=create_prefix,
240
force_new_repo=force_new_repo, stacked_on=stacked_on,
241
stack_on_pwd=stack_on_pwd, repo_format_name=repo_format_name,
242
make_working_trees=make_working_trees, shared_repo=shared_repo,
244
except errors.ErrorFromSmartServer, err:
245
_translate_error(err, path=path)
246
repo_path = response[0]
247
bzrdir_name = response[6]
248
require_stacking = response[7]
249
require_stacking = self.parse_NoneTrueFalse(require_stacking)
250
format = RemoteBzrDirFormat()
251
format._network_name = bzrdir_name
252
self._supply_sub_formats_to(format)
253
bzrdir = RemoteBzrDir(transport, format, _client=client)
255
repo_format = response_tuple_to_repo_format(response[1:])
259
repo_bzrdir_format = RemoteBzrDirFormat()
260
repo_bzrdir_format._network_name = response[5]
261
repo_bzr = RemoteBzrDir(transport.clone(repo_path),
265
final_stack = response[8] or None
266
final_stack_pwd = response[9] or None
268
final_stack_pwd = urlutils.join(
269
transport.base, final_stack_pwd)
270
remote_repo = RemoteRepository(repo_bzr, repo_format)
271
if len(response) > 10:
272
# Updated server verb that locks remotely.
273
repo_lock_token = response[10] or None
274
remote_repo.lock_write(repo_lock_token, _skip_rpc=True)
276
remote_repo.dont_leave_lock_in_place()
278
remote_repo.lock_write()
279
policy = _mod_bzrdir.UseExistingRepository(remote_repo, final_stack,
280
final_stack_pwd, require_stacking)
281
policy.acquire_repository()
285
bzrdir._format.set_branch_format(self.get_branch_format())
287
# The repo has already been created, but we need to make sure that
288
# we'll make a stackable branch.
289
bzrdir._format.require_stacking(_skip_repo=True)
290
return remote_repo, bzrdir, require_stacking, policy
292
def _open(self, transport):
293
return RemoteBzrDir(transport, self)
295
def __eq__(self, other):
296
if not isinstance(other, RemoteBzrDirFormat):
298
return self.get_format_description() == other.get_format_description()
300
def __return_repository_format(self):
301
# Always return a RemoteRepositoryFormat object, but if a specific bzr
302
# repository format has been asked for, tell the RemoteRepositoryFormat
303
# that it should use that for init() etc.
304
result = RemoteRepositoryFormat()
305
custom_format = getattr(self, '_repository_format', None)
307
if isinstance(custom_format, RemoteRepositoryFormat):
310
# We will use the custom format to create repositories over the
311
# wire; expose its details like rich_root_data for code to
313
result._custom_format = custom_format
316
def get_branch_format(self):
317
result = _mod_bzrdir.BzrDirMetaFormat1.get_branch_format(self)
318
if not isinstance(result, RemoteBranchFormat):
319
new_result = RemoteBranchFormat()
320
new_result._custom_format = result
322
self.set_branch_format(new_result)
326
repository_format = property(__return_repository_format,
327
_mod_bzrdir.BzrDirMetaFormat1._set_repository_format) #.im_func)
330
class RemoteBzrDir(_mod_bzrdir.BzrDir, _RpcHelper):
30
from bzrlib.smart import client, vfs
31
from bzrlib.trace import note
33
# Note: RemoteBzrDirFormat is in bzrdir.py
35
class RemoteBzrDir(BzrDir):
331
36
"""Control directory on a remote server, accessed via bzr:// or similar."""
333
def __init__(self, transport, format, _client=None, _force_probe=False):
38
def __init__(self, transport, _client=None):
334
39
"""Construct a RemoteBzrDir.
336
41
:param _client: Private parameter for testing. Disables probing and the
337
42
use of a real bzrdir.
339
_mod_bzrdir.BzrDir.__init__(self, transport, format)
44
BzrDir.__init__(self, transport, RemoteBzrDirFormat())
340
45
# this object holds a delegated bzrdir that uses file-level operations
341
46
# to talk to the other side
342
47
self._real_bzrdir = None
343
self._has_working_tree = None
344
# 1-shot cache for the call pattern 'create_branch; open_branch' - see
345
# create_branch for details.
346
self._next_open_branch_result = None
348
49
if _client is None:
349
medium = transport.get_smart_medium()
350
self._client = client._SmartClient(medium)
50
self._medium = transport.get_smart_client()
51
self._client = client._SmartClient(self._medium)
352
53
self._client = _client
359
return '%s(%r)' % (self.__class__.__name__, self._client)
361
def _probe_bzrdir(self):
362
medium = self._client._medium
363
57
path = self._path_for_remote_call(self._client)
364
if medium._is_remote_before((2, 1)):
368
self._rpc_open_2_1(path)
370
except errors.UnknownSmartMethod:
371
medium._remember_remote_is_before((2, 1))
374
def _rpc_open_2_1(self, path):
375
response = self._call('BzrDir.open_2.1', path)
376
if response == ('no',):
377
raise errors.NotBranchError(path=self.root_transport.base)
378
elif response[0] == 'yes':
379
if response[1] == 'yes':
380
self._has_working_tree = True
381
elif response[1] == 'no':
382
self._has_working_tree = False
384
raise errors.UnexpectedSmartServerResponse(response)
386
raise errors.UnexpectedSmartServerResponse(response)
388
def _rpc_open(self, path):
389
response = self._call('BzrDir.open', path)
58
response = self._client.call('BzrDir.open', path)
390
59
if response not in [('yes',), ('no',)]:
391
60
raise errors.UnexpectedSmartServerResponse(response)
392
61
if response == ('no',):
393
raise errors.NotBranchError(path=self.root_transport.base)
62
raise errors.NotBranchError(path=transport.base)
395
64
def _ensure_real(self):
396
65
"""Ensure that there is a _real_bzrdir set.
398
67
Used before calls to self._real_bzrdir.
400
69
if not self._real_bzrdir:
401
if 'hpssvfs' in debug.debug_flags:
403
warning('VFS BzrDir access triggered\n%s',
404
''.join(traceback.format_stack()))
405
self._real_bzrdir = _mod_bzrdir.BzrDir.open_from_transport(
70
self._real_bzrdir = BzrDir.open_from_transport(
406
71
self.root_transport, _server_formats=False)
407
self._format._network_name = \
408
self._real_bzrdir._format.network_name()
410
def _translate_error(self, err, **context):
411
_translate_error(err, bzrdir=self, **context)
413
def break_lock(self):
414
# Prevent aliasing problems in the next_open_branch_result cache.
415
# See create_branch for rationale.
416
self._next_open_branch_result = None
417
return _mod_bzrdir.BzrDir.break_lock(self)
419
def _vfs_cloning_metadir(self, require_stacking=False):
421
return self._real_bzrdir.cloning_metadir(
422
require_stacking=require_stacking)
424
def cloning_metadir(self, require_stacking=False):
425
medium = self._client._medium
426
if medium._is_remote_before((1, 13)):
427
return self._vfs_cloning_metadir(require_stacking=require_stacking)
428
verb = 'BzrDir.cloning_metadir'
433
path = self._path_for_remote_call(self._client)
435
response = self._call(verb, path, stacking)
436
except errors.UnknownSmartMethod:
437
medium._remember_remote_is_before((1, 13))
438
return self._vfs_cloning_metadir(require_stacking=require_stacking)
439
except errors.UnknownErrorFromSmartServer, err:
440
if err.error_tuple != ('BranchReference',):
442
# We need to resolve the branch reference to determine the
443
# cloning_metadir. This causes unnecessary RPCs to open the
444
# referenced branch (and bzrdir, etc) but only when the caller
445
# didn't already resolve the branch reference.
446
referenced_branch = self.open_branch()
447
return referenced_branch.bzrdir.cloning_metadir()
448
if len(response) != 3:
449
raise errors.UnexpectedSmartServerResponse(response)
450
control_name, repo_name, branch_info = response
451
if len(branch_info) != 2:
452
raise errors.UnexpectedSmartServerResponse(response)
453
branch_ref, branch_name = branch_info
454
format = controldir.network_format_registry.get(control_name)
456
format.repository_format = _mod_repository.network_format_registry.get(
458
if branch_ref == 'ref':
459
# XXX: we need possible_transports here to avoid reopening the
460
# connection to the referenced location
461
ref_bzrdir = _mod_bzrdir.BzrDir.open(branch_name)
462
branch_format = ref_bzrdir.cloning_metadir().get_branch_format()
463
format.set_branch_format(branch_format)
464
elif branch_ref == 'branch':
466
format.set_branch_format(
467
branch.network_format_registry.get(branch_name))
469
raise errors.UnexpectedSmartServerResponse(response)
472
73
def create_repository(self, shared=False):
473
# as per meta1 formats - just delegate to the format object which may
475
result = self._format.repository_format.initialize(self, shared)
476
if not isinstance(result, RemoteRepository):
477
return self.open_repository()
481
def destroy_repository(self):
482
"""See BzrDir.destroy_repository"""
484
self._real_bzrdir.destroy_repository()
486
def create_branch(self, name=None, repository=None):
487
# as per meta1 formats - just delegate to the format object which may
489
real_branch = self._format.get_branch_format().initialize(self,
490
name=name, repository=repository)
491
if not isinstance(real_branch, RemoteBranch):
492
if not isinstance(repository, RemoteRepository):
493
raise AssertionError(
494
'need a RemoteRepository to use with RemoteBranch, got %r'
496
result = RemoteBranch(self, repository, real_branch, name=name)
499
# BzrDir.clone_on_transport() uses the result of create_branch but does
500
# not return it to its callers; we save approximately 8% of our round
501
# trips by handing the branch we created back to the first caller to
502
# open_branch rather than probing anew. Long term we need a API in
503
# bzrdir that doesn't discard result objects (like result_branch).
505
self._next_open_branch_result = result
508
def destroy_branch(self, name=None):
509
"""See BzrDir.destroy_branch"""
511
self._real_bzrdir.destroy_branch(name=name)
512
self._next_open_branch_result = None
514
def create_workingtree(self, revision_id=None, from_branch=None,
515
accelerator_tree=None, hardlink=False):
75
self._real_bzrdir.create_repository(shared=shared)
76
return self.open_repository()
78
def create_branch(self):
80
real_branch = self._real_bzrdir.create_branch()
81
return RemoteBranch(self, self.find_repository(), real_branch)
83
def create_workingtree(self, revision_id=None):
516
84
raise errors.NotLocalUrl(self.transport.base)
518
def find_branch_format(self, name=None):
86
def find_branch_format(self):
519
87
"""Find the branch 'format' for this bzrdir.
521
89
This might be a synthetic object for e.g. RemoteBranch and SVN.
523
b = self.open_branch(name=name)
91
b = self.open_branch()
526
def get_branch_reference(self, name=None):
94
def get_branch_reference(self):
527
95
"""See BzrDir.get_branch_reference()."""
529
# XXX JRV20100304: Support opening colocated branches
530
raise errors.NoColocatedBranchSupport(self)
531
response = self._get_branch_reference()
532
if response[0] == 'ref':
537
def _get_branch_reference(self):
538
96
path = self._path_for_remote_call(self._client)
539
medium = self._client._medium
541
('BzrDir.open_branchV3', (2, 1)),
542
('BzrDir.open_branchV2', (1, 13)),
543
('BzrDir.open_branch', None),
545
for verb, required_version in candidate_calls:
546
if required_version and medium._is_remote_before(required_version):
549
response = self._call(verb, path)
550
except errors.UnknownSmartMethod:
551
if required_version is None:
553
medium._remember_remote_is_before(required_version)
556
if verb == 'BzrDir.open_branch':
557
if response[0] != 'ok':
558
raise errors.UnexpectedSmartServerResponse(response)
559
if response[1] != '':
560
return ('ref', response[1])
562
return ('branch', '')
563
if response[0] not in ('ref', 'branch'):
564
raise errors.UnexpectedSmartServerResponse(response)
567
def _get_tree_branch(self, name=None):
568
"""See BzrDir._get_tree_branch()."""
569
return None, self.open_branch(name=name)
571
def open_branch(self, name=None, unsupported=False,
572
ignore_fallbacks=False):
574
raise NotImplementedError('unsupported flag support not implemented yet.')
575
if self._next_open_branch_result is not None:
576
# See create_branch for details.
577
result = self._next_open_branch_result
578
self._next_open_branch_result = None
580
response = self._get_branch_reference()
581
if response[0] == 'ref':
97
response = self._client.call('BzrDir.open_branch', path)
98
if response[0] == 'ok':
100
# branch at this location.
103
# a branch reference, use the existing BranchReference logic.
105
elif response == ('nobranch',):
106
raise errors.NotBranchError(path=self.root_transport.base)
108
assert False, 'unexpected response code %r' % (response,)
110
def open_branch(self, _unsupported=False):
111
assert _unsupported == False, 'unsupported flag support not implemented yet.'
112
reference_url = self.get_branch_reference()
113
if reference_url is None:
114
# branch at this location.
115
return RemoteBranch(self, self.find_repository())
582
117
# a branch reference, use the existing BranchReference logic.
583
118
format = BranchReferenceFormat()
584
return format.open(self, name=name, _found=True,
585
location=response[1], ignore_fallbacks=ignore_fallbacks)
586
branch_format_name = response[1]
587
if not branch_format_name:
588
branch_format_name = None
589
format = RemoteBranchFormat(network_name=branch_format_name)
590
return RemoteBranch(self, self.find_repository(), format=format,
591
setup_stacking=not ignore_fallbacks, name=name)
593
def _open_repo_v1(self, path):
594
verb = 'BzrDir.find_repository'
595
response = self._call(verb, path)
596
if response[0] != 'ok':
597
raise errors.UnexpectedSmartServerResponse(response)
598
# servers that only support the v1 method don't support external
601
repo = self._real_bzrdir.open_repository()
602
response = response + ('no', repo._format.network_name())
603
return response, repo
605
def _open_repo_v2(self, path):
606
verb = 'BzrDir.find_repositoryV2'
607
response = self._call(verb, path)
608
if response[0] != 'ok':
609
raise errors.UnexpectedSmartServerResponse(response)
611
repo = self._real_bzrdir.open_repository()
612
response = response + (repo._format.network_name(),)
613
return response, repo
615
def _open_repo_v3(self, path):
616
verb = 'BzrDir.find_repositoryV3'
617
medium = self._client._medium
618
if medium._is_remote_before((1, 13)):
619
raise errors.UnknownSmartMethod(verb)
621
response = self._call(verb, path)
622
except errors.UnknownSmartMethod:
623
medium._remember_remote_is_before((1, 13))
625
if response[0] != 'ok':
626
raise errors.UnexpectedSmartServerResponse(response)
627
return response, None
119
return format.open(self, _found=True, location=reference_url)
629
121
def open_repository(self):
630
122
path = self._path_for_remote_call(self._client)
632
for probe in [self._open_repo_v3, self._open_repo_v2,
635
response, real_repo = probe(path)
637
except errors.UnknownSmartMethod:
640
raise errors.UnknownSmartMethod('BzrDir.find_repository{3,2,}')
641
if response[0] != 'ok':
642
raise errors.UnexpectedSmartServerResponse(response)
643
if len(response) != 6:
644
raise SmartProtocolError('incorrect response length %s' % (response,))
123
response = self._client.call('BzrDir.find_repository', path)
124
assert response[0] in ('ok', 'norepository'), \
125
'unexpected response code %s' % (response,)
126
if response[0] == 'norepository':
127
raise errors.NoRepositoryPresent(self)
128
assert len(response) == 4, 'incorrect response length %s' % (response,)
645
129
if response[1] == '':
646
# repo is at this dir.
647
format = response_tuple_to_repo_format(response[2:])
648
# Used to support creating a real format instance when needed.
649
format._creating_bzrdir = self
650
remote_repo = RemoteRepository(self, format)
651
format._creating_repo = remote_repo
652
if real_repo is not None:
653
remote_repo._set_real_repository(real_repo)
130
format = RemoteRepositoryFormat()
131
format.rich_root_data = (response[2] == 'yes')
132
format.supports_tree_reference = (response[3] == 'yes')
133
return RemoteRepository(self, format)
656
135
raise errors.NoRepositoryPresent(self)
658
def has_workingtree(self):
659
if self._has_working_tree is None:
661
self._has_working_tree = self._real_bzrdir.has_workingtree()
662
return self._has_working_tree
664
137
def open_workingtree(self, recommend_upgrade=True):
665
if self.has_workingtree():
139
if self._real_bzrdir.has_workingtree():
666
140
raise errors.NotLocalUrl(self.root_transport)
668
142
raise errors.NoWorkingTree(self.root_transport.base)
670
144
def _path_for_remote_call(self, client):
671
145
"""Return the path to be used for this bzrdir in a remote call."""
672
return urlutils.split_segment_parameters_raw(
673
client.remote_path_from_transport(self.root_transport))[0]
146
return client.remote_path_from_transport(self.root_transport)
675
def get_branch_transport(self, branch_format, name=None):
148
def get_branch_transport(self, branch_format):
676
149
self._ensure_real()
677
return self._real_bzrdir.get_branch_transport(branch_format, name=name)
150
return self._real_bzrdir.get_branch_transport(branch_format)
679
152
def get_repository_transport(self, repository_format):
680
153
self._ensure_real()
688
161
"""Upgrading of remote bzrdirs is not supported yet."""
691
def needs_format_conversion(self, format):
164
def needs_format_conversion(self, format=None):
692
165
"""Upgrading of remote bzrdirs is not supported yet."""
695
def clone(self, url, revision_id=None, force_new_repo=False,
696
preserve_stacking=False):
168
def clone(self, url, revision_id=None, force_new_repo=False):
697
169
self._ensure_real()
698
170
return self._real_bzrdir.clone(url, revision_id=revision_id,
699
force_new_repo=force_new_repo, preserve_stacking=preserve_stacking)
701
def _get_config(self):
702
return RemoteBzrDirConfig(self)
705
class RemoteRepositoryFormat(vf_repository.VersionedFileRepositoryFormat):
171
force_new_repo=force_new_repo)
174
class RemoteRepositoryFormat(repository.RepositoryFormat):
706
175
"""Format for repositories accessed over a _SmartClient.
708
177
Instances of this repository are represented by RemoteRepository
711
The RemoteRepositoryFormat is parameterized during construction
180
The RemoteRepositoryFormat is parameterised during construction
712
181
to reflect the capabilities of the real, remote format. Specifically
713
182
the attributes rich_root_data and supports_tree_reference are set
714
183
on a per instance basis, and are not set (and should not be) at
717
:ivar _custom_format: If set, a specific concrete repository format that
718
will be used when initializing a repository with this
719
RemoteRepositoryFormat.
720
:ivar _creating_repo: If set, the repository object that this
721
RemoteRepositoryFormat was created for: it can be called into
722
to obtain data like the network name.
725
_matchingbzrdir = RemoteBzrDirFormat()
726
supports_full_versioned_files = True
727
supports_leaving_lock = True
730
_mod_repository.RepositoryFormat.__init__(self)
731
self._custom_format = None
732
self._network_name = None
733
self._creating_bzrdir = None
734
self._revision_graph_can_have_wrong_parents = None
735
self._supports_chks = None
736
self._supports_external_lookups = None
737
self._supports_tree_reference = None
738
self._supports_funky_characters = None
739
self._rich_root_data = None
742
return "%s(_network_name=%r)" % (self.__class__.__name__,
746
def fast_deltas(self):
748
return self._custom_format.fast_deltas
751
def rich_root_data(self):
752
if self._rich_root_data is None:
754
self._rich_root_data = self._custom_format.rich_root_data
755
return self._rich_root_data
758
def supports_chks(self):
759
if self._supports_chks is None:
761
self._supports_chks = self._custom_format.supports_chks
762
return self._supports_chks
765
def supports_external_lookups(self):
766
if self._supports_external_lookups is None:
768
self._supports_external_lookups = \
769
self._custom_format.supports_external_lookups
770
return self._supports_external_lookups
773
def supports_funky_characters(self):
774
if self._supports_funky_characters is None:
776
self._supports_funky_characters = \
777
self._custom_format.supports_funky_characters
778
return self._supports_funky_characters
781
def supports_tree_reference(self):
782
if self._supports_tree_reference is None:
784
self._supports_tree_reference = \
785
self._custom_format.supports_tree_reference
786
return self._supports_tree_reference
789
def revision_graph_can_have_wrong_parents(self):
790
if self._revision_graph_can_have_wrong_parents is None:
792
self._revision_graph_can_have_wrong_parents = \
793
self._custom_format.revision_graph_can_have_wrong_parents
794
return self._revision_graph_can_have_wrong_parents
796
def _vfs_initialize(self, a_bzrdir, shared):
797
"""Helper for common code in initialize."""
798
if self._custom_format:
799
# Custom format requested
800
result = self._custom_format.initialize(a_bzrdir, shared=shared)
801
elif self._creating_bzrdir is not None:
802
# Use the format that the repository we were created to back
804
prior_repo = self._creating_bzrdir.open_repository()
805
prior_repo._ensure_real()
806
result = prior_repo._real_repository._format.initialize(
807
a_bzrdir, shared=shared)
809
# assume that a_bzr is a RemoteBzrDir but the smart server didn't
810
# support remote initialization.
811
# We delegate to a real object at this point (as RemoteBzrDir
812
# delegate to the repository format which would lead to infinite
813
# recursion if we just called a_bzrdir.create_repository.
814
a_bzrdir._ensure_real()
815
result = a_bzrdir._real_bzrdir.create_repository(shared=shared)
816
if not isinstance(result, RemoteRepository):
817
return self.open(a_bzrdir)
187
_matchingbzrdir = RemoteBzrDirFormat
821
189
def initialize(self, a_bzrdir, shared=False):
822
# Being asked to create on a non RemoteBzrDir:
823
if not isinstance(a_bzrdir, RemoteBzrDir):
824
return self._vfs_initialize(a_bzrdir, shared)
825
medium = a_bzrdir._client._medium
826
if medium._is_remote_before((1, 13)):
827
return self._vfs_initialize(a_bzrdir, shared)
828
# Creating on a remote bzr dir.
829
# 1) get the network name to use.
830
if self._custom_format:
831
network_name = self._custom_format.network_name()
832
elif self._network_name:
833
network_name = self._network_name
835
# Select the current bzrlib default and ask for that.
836
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
837
reference_format = reference_bzrdir_format.repository_format
838
network_name = reference_format.network_name()
839
# 2) try direct creation via RPC
840
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
841
verb = 'BzrDir.create_repository'
847
response = a_bzrdir._call(verb, path, network_name, shared_str)
848
except errors.UnknownSmartMethod:
849
# Fallback - use vfs methods
850
medium._remember_remote_is_before((1, 13))
851
return self._vfs_initialize(a_bzrdir, shared)
853
# Turn the response into a RemoteRepository object.
854
format = response_tuple_to_repo_format(response[1:])
855
# Used to support creating a real format instance when needed.
856
format._creating_bzrdir = a_bzrdir
857
remote_repo = RemoteRepository(a_bzrdir, format)
858
format._creating_repo = remote_repo
190
assert isinstance(a_bzrdir, RemoteBzrDir), \
191
'%r is not a RemoteBzrDir' % (a_bzrdir,)
192
return a_bzrdir.create_repository(shared=shared)
861
194
def open(self, a_bzrdir):
862
if not isinstance(a_bzrdir, RemoteBzrDir):
863
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
195
assert isinstance(a_bzrdir, RemoteBzrDir)
864
196
return a_bzrdir.open_repository()
866
def _ensure_real(self):
867
if self._custom_format is None:
868
self._custom_format = _mod_repository.network_format_registry.get(
872
def _fetch_order(self):
874
return self._custom_format._fetch_order
877
def _fetch_uses_deltas(self):
879
return self._custom_format._fetch_uses_deltas
882
def _fetch_reconcile(self):
884
return self._custom_format._fetch_reconcile
886
198
def get_format_description(self):
888
return 'Remote: ' + self._custom_format.get_format_description()
199
return 'bzr remote repository'
890
201
def __eq__(self, other):
891
return self.__class__ is other.__class__
893
def network_name(self):
894
if self._network_name:
895
return self._network_name
896
self._creating_repo._ensure_real()
897
return self._creating_repo._real_repository._format.network_name()
900
def pack_compresses(self):
902
return self._custom_format.pack_compresses
905
def _serializer(self):
907
return self._custom_format._serializer
910
class RemoteRepository(_RpcHelper, lock._RelockDebugMixin,
911
controldir.ControlComponent):
202
return self.__class__ == other.__class__
204
def check_conversion_target(self, target_format):
205
if self.rich_root_data and not target_format.rich_root_data:
206
raise errors.BadConversionTarget(
207
'Does not support rich root data.', target_format)
208
if (self.supports_tree_reference and
209
not getattr(target_format, 'supports_tree_reference', False)):
210
raise errors.BadConversionTarget(
211
'Does not support nested trees', target_format)
214
class RemoteRepository(object):
912
215
"""Repository accessed over rpc.
914
217
For the moment most operations are performed using local transport-backed
940
243
self._lock_token = None
941
244
self._lock_count = 0
942
245
self._leave_lock = False
943
# Cache of revision parents; misses are cached during read locks, and
944
# write locks when no _real_repository has been set.
945
self._unstacked_provider = graph.CachingParentsProvider(
946
get_parent_map=self._get_parent_map_rpc)
947
self._unstacked_provider.disable_cache()
949
# These depend on the actual remote format, so force them off for
950
# maximum compatibility. XXX: In future these should depend on the
951
# remote repository instance, but this is irrelevant until we perform
952
# reconcile via an RPC call.
953
self._reconcile_does_inventory_gc = False
954
self._reconcile_fixes_text_parents = False
955
self._reconcile_backsup_inventory = False
956
self.base = self.bzrdir.transport.base
957
# Additional places to query for data.
958
self._fallback_repositories = []
961
def user_transport(self):
962
return self.bzrdir.user_transport
965
def control_transport(self):
966
# XXX: Normally you shouldn't directly get at the remote repository
967
# transport, but I'm not sure it's worth making this method
968
# optional -- mbp 2010-04-21
969
return self.bzrdir.get_repository_transport(None)
972
return "%s(%s)" % (self.__class__.__name__, self.base)
976
def abort_write_group(self, suppress_errors=False):
977
"""Complete a write group on the decorated repository.
979
Smart methods perform operations in a single step so this API
980
is not really applicable except as a compatibility thunk
981
for older plugins that don't use e.g. the CommitBuilder
984
:param suppress_errors: see Repository.abort_write_group.
987
return self._real_repository.abort_write_group(
988
suppress_errors=suppress_errors)
992
"""Decorate the real repository for now.
994
In the long term a full blown network facility is needed to avoid
995
creating a real repository object locally.
998
return self._real_repository.chk_bytes
1000
def commit_write_group(self):
1001
"""Complete a write group on the decorated repository.
1003
Smart methods perform operations in a single step so this API
1004
is not really applicable except as a compatibility thunk
1005
for older plugins that don't use e.g. the CommitBuilder
1009
return self._real_repository.commit_write_group()
1011
def resume_write_group(self, tokens):
1013
return self._real_repository.resume_write_group(tokens)
1015
def suspend_write_group(self):
1017
return self._real_repository.suspend_write_group()
1019
def get_missing_parent_inventories(self, check_for_missing_texts=True):
1021
return self._real_repository.get_missing_parent_inventories(
1022
check_for_missing_texts=check_for_missing_texts)
1024
def _get_rev_id_for_revno_vfs(self, revno, known_pair):
1026
return self._real_repository.get_rev_id_for_revno(
1029
def get_rev_id_for_revno(self, revno, known_pair):
1030
"""See Repository.get_rev_id_for_revno."""
1031
path = self.bzrdir._path_for_remote_call(self._client)
1033
if self._client._medium._is_remote_before((1, 17)):
1034
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1035
response = self._call(
1036
'Repository.get_rev_id_for_revno', path, revno, known_pair)
1037
except errors.UnknownSmartMethod:
1038
self._client._medium._remember_remote_is_before((1, 17))
1039
return self._get_rev_id_for_revno_vfs(revno, known_pair)
1040
if response[0] == 'ok':
1041
return True, response[1]
1042
elif response[0] == 'history-incomplete':
1043
known_pair = response[1:3]
1044
for fallback in self._fallback_repositories:
1045
found, result = fallback.get_rev_id_for_revno(revno, known_pair)
1050
# Not found in any fallbacks
1051
return False, known_pair
1053
raise errors.UnexpectedSmartServerResponse(response)
1055
247
def _ensure_real(self):
1056
248
"""Ensure that there is a _real_repository set.
1058
250
Used before calls to self._real_repository.
1060
Note that _ensure_real causes many roundtrips to the server which are
1061
not desirable, and prevents the use of smart one-roundtrip RPC's to
1062
perform complex operations (such as accessing parent data, streaming
1063
revisions etc). Adding calls to _ensure_real should only be done when
1064
bringing up new functionality, adding fallbacks for smart methods that
1065
require a fallback path, and never to replace an existing smart method
1066
invocation. If in doubt chat to the bzr network team.
1068
if self._real_repository is None:
1069
if 'hpssvfs' in debug.debug_flags:
1071
warning('VFS Repository access triggered\n%s',
1072
''.join(traceback.format_stack()))
1073
self._unstacked_provider.missing_keys.clear()
252
if not self._real_repository:
1074
253
self.bzrdir._ensure_real()
1075
self._set_real_repository(
1076
self.bzrdir._real_bzrdir.open_repository())
1078
def _translate_error(self, err, **context):
1079
self.bzrdir._translate_error(err, repository=self, **context)
1081
def find_text_key_references(self):
1082
"""Find the text key references within the repository.
1084
:return: A dictionary mapping text keys ((fileid, revision_id) tuples)
1085
to whether they were referred to by the inventory of the
1086
revision_id that they contain. The inventory texts from all present
1087
revision ids are assessed to generate this report.
1090
return self._real_repository.find_text_key_references()
1092
def _generate_text_key_index(self):
1093
"""Generate a new text key index for the repository.
1095
This is an expensive function that will take considerable time to run.
1097
:return: A dict mapping (file_id, revision_id) tuples to a list of
1098
parents, also (file_id, revision_id) tuples.
1101
return self._real_repository._generate_text_key_index()
1103
def _get_revision_graph(self, revision_id):
1104
"""Private method for using with old (< 1.2) servers to fallback."""
254
#self._real_repository = self.bzrdir._real_bzrdir.open_repository()
255
self._set_real_repository(self.bzrdir._real_bzrdir.open_repository())
257
def get_revision_graph(self, revision_id=None):
258
"""See Repository.get_revision_graph()."""
1105
259
if revision_id is None:
1106
260
revision_id = ''
1107
elif _mod_revision.is_null(revision_id):
261
elif revision_id == NULL_REVISION:
1110
264
path = self.bzrdir._path_for_remote_call(self._client)
1111
response = self._call_expecting_body(
265
assert type(revision_id) is str
266
response = self._client.call_expecting_body(
1112
267
'Repository.get_revision_graph', path, revision_id)
1113
response_tuple, response_handler = response
1114
if response_tuple[0] != 'ok':
1115
raise errors.UnexpectedSmartServerResponse(response_tuple)
1116
coded = response_handler.read_body_bytes()
1118
# no revisions in this repository!
1120
lines = coded.split('\n')
1123
d = tuple(line.split())
1124
revision_graph[d[0]] = d[1:]
1126
return revision_graph
1128
def _get_sink(self):
1129
"""See Repository._get_sink()."""
1130
return RemoteStreamSink(self)
1132
def _get_source(self, to_format):
1133
"""Return a source for streaming from this repository."""
1134
return RemoteStreamSource(self, to_format)
1137
def get_file_graph(self):
1138
return graph.Graph(self.texts)
268
if response[0][0] not in ['ok', 'nosuchrevision']:
269
raise errors.UnexpectedSmartServerResponse(response[0])
270
if response[0][0] == 'ok':
271
coded = response[1].read_body_bytes()
273
# no revisions in this repository!
275
lines = coded.split('\n')
278
d = list(line.split())
279
revision_graph[d[0]] = d[1:]
281
return revision_graph
283
response_body = response[1].read_body_bytes()
284
assert response_body == ''
285
raise NoSuchRevision(self, revision_id)
1141
287
def has_revision(self, revision_id):
1142
"""True if this repository has a copy of the revision."""
1143
# Copy of bzrlib.repository.Repository.has_revision
1144
return revision_id in self.has_revisions((revision_id,))
1147
def has_revisions(self, revision_ids):
1148
"""Probe to find out the presence of multiple revisions.
1150
:param revision_ids: An iterable of revision_ids.
1151
:return: A set of the revision_ids that were present.
1153
# Copy of bzrlib.repository.Repository.has_revisions
1154
parent_map = self.get_parent_map(revision_ids)
1155
result = set(parent_map)
1156
if _mod_revision.NULL_REVISION in revision_ids:
1157
result.add(_mod_revision.NULL_REVISION)
1160
def _has_same_fallbacks(self, other_repo):
1161
"""Returns true if the repositories have the same fallbacks."""
1162
# XXX: copied from Repository; it should be unified into a base class
1163
# <https://bugs.launchpad.net/bzr/+bug/401622>
1164
my_fb = self._fallback_repositories
1165
other_fb = other_repo._fallback_repositories
1166
if len(my_fb) != len(other_fb):
1168
for f, g in zip(my_fb, other_fb):
1169
if not f.has_same_location(g):
1173
def has_same_location(self, other):
1174
# TODO: Move to RepositoryBase and unify with the regular Repository
1175
# one; unfortunately the tests rely on slightly different behaviour at
1176
# present -- mbp 20090710
1177
return (self.__class__ is other.__class__ and
1178
self.bzrdir.transport.base == other.bzrdir.transport.base)
1180
def get_graph(self, other_repository=None):
1181
"""Return the graph for this repository format"""
1182
parents_provider = self._make_parents_provider(other_repository)
1183
return graph.Graph(parents_provider)
1186
def get_known_graph_ancestry(self, revision_ids):
1187
"""Return the known graph for a set of revision ids and their ancestors.
1189
st = static_tuple.StaticTuple
1190
revision_keys = [st(r_id).intern() for r_id in revision_ids]
1191
known_graph = self.revisions.get_known_graph_ancestry(revision_keys)
1192
return graph.GraphThunkIdsToKeys(known_graph)
288
"""See Repository.has_revision()."""
289
if revision_id is None:
290
# The null revision is always present.
292
path = self.bzrdir._path_for_remote_call(self._client)
293
response = self._client.call('Repository.has_revision', path, revision_id)
294
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
295
return response[0] == 'yes'
1194
297
def gather_stats(self, revid=None, committers=None):
1195
298
"""See Repository.gather_stats()."""
1196
299
path = self.bzrdir._path_for_remote_call(self._client)
1197
# revid can be None to indicate no revisions, not just NULL_REVISION
1198
if revid is None or _mod_revision.is_null(revid):
300
if revid in (None, NULL_REVISION):
1201
303
fmt_revid = revid
1572
510
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
1574
512
def make_working_trees(self):
1575
"""See Repository.make_working_trees"""
1577
return self._real_repository.make_working_trees()
1579
def refresh_data(self):
1580
"""Re-read any data needed to synchronise with disk.
1582
This method is intended to be called after another repository instance
1583
(such as one used by a smart server) has inserted data into the
1584
repository. On all repositories this will work outside of write groups.
1585
Some repository formats (pack and newer for bzrlib native formats)
1586
support refresh_data inside write groups. If called inside a write
1587
group on a repository that does not support refreshing in a write group
1588
IsInWriteGroupError will be raised.
1590
if self._real_repository is not None:
1591
self._real_repository.refresh_data()
1593
def revision_ids_to_search_result(self, result_set):
1594
"""Convert a set of revision ids to a graph SearchResult."""
1595
result_parents = set()
1596
for parents in self.get_graph().get_parent_map(
1597
result_set).itervalues():
1598
result_parents.update(parents)
1599
included_keys = result_set.intersection(result_parents)
1600
start_keys = result_set.difference(included_keys)
1601
exclude_keys = result_parents.difference(result_set)
1602
result = graph.SearchResult(start_keys, exclude_keys,
1603
len(result_set), result_set)
1607
def search_missing_revision_ids(self, other,
1608
revision_id=symbol_versioning.DEPRECATED_PARAMETER,
1609
find_ghosts=True, revision_ids=None, if_present_ids=None,
1611
"""Return the revision ids that other has that this does not.
1613
These are returned in topological order.
1615
revision_id: only return revision ids included by revision_id.
1617
if symbol_versioning.deprecated_passed(revision_id):
1618
symbol_versioning.warn(
1619
'search_missing_revision_ids(revision_id=...) was '
1620
'deprecated in 2.4. Use revision_ids=[...] instead.',
1621
DeprecationWarning, stacklevel=2)
1622
if revision_ids is not None:
1623
raise AssertionError(
1624
'revision_ids is mutually exclusive with revision_id')
1625
if revision_id is not None:
1626
revision_ids = [revision_id]
1627
inter_repo = _mod_repository.InterRepository.get(other, self)
1628
return inter_repo.search_missing_revision_ids(
1629
find_ghosts=find_ghosts, revision_ids=revision_ids,
1630
if_present_ids=if_present_ids, limit=limit)
1632
def fetch(self, source, revision_id=None, find_ghosts=False,
1634
# No base implementation to use as RemoteRepository is not a subclass
1635
# of Repository; so this is a copy of Repository.fetch().
1636
if fetch_spec is not None and revision_id is not None:
1637
raise AssertionError(
1638
"fetch_spec and revision_id are mutually exclusive.")
1639
if self.is_in_write_group():
1640
raise errors.InternalBzrError(
1641
"May not fetch while in a write group.")
1642
# fast path same-url fetch operations
1643
if (self.has_same_location(source)
1644
and fetch_spec is None
1645
and self._has_same_fallbacks(source)):
1646
# check that last_revision is in 'from' and then return a
1648
if (revision_id is not None and
1649
not _mod_revision.is_null(revision_id)):
1650
self.get_revision(revision_id)
1652
# if there is no specific appropriate InterRepository, this will get
1653
# the InterRepository base class, which raises an
1654
# IncompatibleRepositories when asked to fetch.
1655
inter = _mod_repository.InterRepository.get(source, self)
1656
return inter.fetch(revision_id=revision_id,
1657
find_ghosts=find_ghosts, fetch_spec=fetch_spec)
1659
def create_bundle(self, target, base, fileobj, format=None):
1661
self._real_repository.create_bundle(target, base, fileobj, format)
1664
@symbol_versioning.deprecated_method(
1665
symbol_versioning.deprecated_in((2, 4, 0)))
1666
def get_ancestry(self, revision_id, topo_sorted=True):
1668
return self._real_repository.get_ancestry(revision_id, topo_sorted)
513
"""RemoteRepositories never create working trees by default."""
516
def fetch(self, source, revision_id=None, pb=None):
518
return self._real_repository.fetch(
519
source, revision_id=revision_id, pb=pb)
522
def control_weaves(self):
524
return self._real_repository.control_weaves
527
def get_ancestry(self, revision_id):
529
return self._real_repository.get_ancestry(revision_id)
532
def get_inventory_weave(self):
534
return self._real_repository.get_inventory_weave()
1670
536
def fileids_altered_by_revision_ids(self, revision_ids):
1671
537
self._ensure_real()
1672
538
return self._real_repository.fileids_altered_by_revision_ids(revision_ids)
1674
def _get_versioned_file_checker(self, revisions, revision_versions_cache):
1676
return self._real_repository._get_versioned_file_checker(
1677
revisions, revision_versions_cache)
1679
def iter_files_bytes(self, desired_files):
1680
"""See Repository.iter_file_bytes.
1683
return self._real_repository.iter_files_bytes(desired_files)
1685
def get_parent_map(self, revision_ids):
1686
"""See bzrlib.Graph.get_parent_map()."""
1687
return self._make_parents_provider().get_parent_map(revision_ids)
1689
def _get_parent_map_rpc(self, keys):
1690
"""Helper for get_parent_map that performs the RPC."""
1691
medium = self._client._medium
1692
if medium._is_remote_before((1, 2)):
1693
# We already found out that the server can't understand
1694
# Repository.get_parent_map requests, so just fetch the whole
1697
# Note that this reads the whole graph, when only some keys are
1698
# wanted. On this old server there's no way (?) to get them all
1699
# in one go, and the user probably will have seen a warning about
1700
# the server being old anyhow.
1701
rg = self._get_revision_graph(None)
1702
# There is an API discrepancy between get_parent_map and
1703
# get_revision_graph. Specifically, a "key:()" pair in
1704
# get_revision_graph just means a node has no parents. For
1705
# "get_parent_map" it means the node is a ghost. So fix up the
1706
# graph to correct this.
1707
# https://bugs.launchpad.net/bzr/+bug/214894
1708
# There is one other "bug" which is that ghosts in
1709
# get_revision_graph() are not returned at all. But we won't worry
1710
# about that for now.
1711
for node_id, parent_ids in rg.iteritems():
1712
if parent_ids == ():
1713
rg[node_id] = (NULL_REVISION,)
1714
rg[NULL_REVISION] = ()
1719
raise ValueError('get_parent_map(None) is not valid')
1720
if NULL_REVISION in keys:
1721
keys.discard(NULL_REVISION)
1722
found_parents = {NULL_REVISION:()}
1724
return found_parents
1727
# TODO(Needs analysis): We could assume that the keys being requested
1728
# from get_parent_map are in a breadth first search, so typically they
1729
# will all be depth N from some common parent, and we don't have to
1730
# have the server iterate from the root parent, but rather from the
1731
# keys we're searching; and just tell the server the keyspace we
1732
# already have; but this may be more traffic again.
1734
# Transform self._parents_map into a search request recipe.
1735
# TODO: Manage this incrementally to avoid covering the same path
1736
# repeatedly. (The server will have to on each request, but the less
1737
# work done the better).
1739
# Negative caching notes:
1740
# new server sends missing when a request including the revid
1741
# 'include-missing:' is present in the request.
1742
# missing keys are serialised as missing:X, and we then call
1743
# provider.note_missing(X) for-all X
1744
parents_map = self._unstacked_provider.get_cached_map()
1745
if parents_map is None:
1746
# Repository is not locked, so there's no cache.
1748
# start_set is all the keys in the cache
1749
start_set = set(parents_map)
1750
# result set is all the references to keys in the cache
1751
result_parents = set()
1752
for parents in parents_map.itervalues():
1753
result_parents.update(parents)
1754
stop_keys = result_parents.difference(start_set)
1755
# We don't need to send ghosts back to the server as a position to
1757
stop_keys.difference_update(self._unstacked_provider.missing_keys)
1758
key_count = len(parents_map)
1759
if (NULL_REVISION in result_parents
1760
and NULL_REVISION in self._unstacked_provider.missing_keys):
1761
# If we pruned NULL_REVISION from the stop_keys because it's also
1762
# in our cache of "missing" keys we need to increment our key count
1763
# by 1, because the reconsitituted SearchResult on the server will
1764
# still consider NULL_REVISION to be an included key.
1766
included_keys = start_set.intersection(result_parents)
1767
start_set.difference_update(included_keys)
1768
recipe = ('manual', start_set, stop_keys, key_count)
1769
body = self._serialise_search_recipe(recipe)
1770
path = self.bzrdir._path_for_remote_call(self._client)
1772
if type(key) is not str:
1774
"key %r not a plain string" % (key,))
1775
verb = 'Repository.get_parent_map'
1776
args = (path, 'include-missing:') + tuple(keys)
1778
response = self._call_with_body_bytes_expecting_body(
1780
except errors.UnknownSmartMethod:
1781
# Server does not support this method, so get the whole graph.
1782
# Worse, we have to force a disconnection, because the server now
1783
# doesn't realise it has a body on the wire to consume, so the
1784
# only way to recover is to abandon the connection.
1786
'Server is too old for fast get_parent_map, reconnecting. '
1787
'(Upgrade the server to Bazaar 1.2 to avoid this)')
1789
# To avoid having to disconnect repeatedly, we keep track of the
1790
# fact the server doesn't understand remote methods added in 1.2.
1791
medium._remember_remote_is_before((1, 2))
1792
# Recurse just once and we should use the fallback code.
1793
return self._get_parent_map_rpc(keys)
1794
response_tuple, response_handler = response
1795
if response_tuple[0] not in ['ok']:
1796
response_handler.cancel_read_body()
1797
raise errors.UnexpectedSmartServerResponse(response_tuple)
1798
if response_tuple[0] == 'ok':
1799
coded = bz2.decompress(response_handler.read_body_bytes())
1801
# no revisions found
1803
lines = coded.split('\n')
1806
d = tuple(line.split())
1808
revision_graph[d[0]] = d[1:]
1811
if d[0].startswith('missing:'):
1813
self._unstacked_provider.note_missing_key(revid)
1815
# no parents - so give the Graph result
1817
revision_graph[d[0]] = (NULL_REVISION,)
1818
return revision_graph
1820
540
@needs_read_lock
1821
541
def get_signature_text(self, revision_id):
1822
542
self._ensure_real()
1823
543
return self._real_repository.get_signature_text(revision_id)
1825
545
@needs_read_lock
1826
def _get_inventory_xml(self, revision_id):
1828
return self._real_repository._get_inventory_xml(revision_id)
546
def get_revision_graph_with_ghosts(self, revision_ids=None):
548
return self._real_repository.get_revision_graph_with_ghosts(
549
revision_ids=revision_ids)
552
def get_inventory_xml(self, revision_id):
554
return self._real_repository.get_inventory_xml(revision_id)
556
def deserialise_inventory(self, revision_id, xml):
558
return self._real_repository.deserialise_inventory(revision_id, xml)
1830
560
def reconcile(self, other=None, thorough=False):
1831
561
self._ensure_real()
1832
562
return self._real_repository.reconcile(other=other, thorough=thorough)
1834
564
def all_revision_ids(self):
1835
565
self._ensure_real()
1836
566
return self._real_repository.all_revision_ids()
1839
def get_deltas_for_revisions(self, revisions, specific_fileids=None):
1841
return self._real_repository.get_deltas_for_revisions(revisions,
1842
specific_fileids=specific_fileids)
1845
def get_revision_delta(self, revision_id, specific_fileids=None):
1847
return self._real_repository.get_revision_delta(revision_id,
1848
specific_fileids=specific_fileids)
569
def get_deltas_for_revisions(self, revisions):
571
return self._real_repository.get_deltas_for_revisions(revisions)
574
def get_revision_delta(self, revision_id):
576
return self._real_repository.get_revision_delta(revision_id)
1850
578
@needs_read_lock
1851
579
def revision_trees(self, revision_ids):
1858
586
return self._real_repository.get_revision_reconcile(revision_id)
1860
588
@needs_read_lock
1861
def check(self, revision_ids=None, callback_refs=None, check_repo=True):
589
def check(self, revision_ids):
1862
590
self._ensure_real()
1863
return self._real_repository.check(revision_ids=revision_ids,
1864
callback_refs=callback_refs, check_repo=check_repo)
591
return self._real_repository.check(revision_ids)
1866
593
def copy_content_into(self, destination, revision_id=None):
1867
594
self._ensure_real()
1868
595
return self._real_repository.copy_content_into(
1869
596
destination, revision_id=revision_id)
1871
def _copy_repository_tarball(self, to_bzrdir, revision_id=None):
598
def _copy_repository_tarball(self, destination, revision_id=None):
1872
599
# get a tarball of the remote repository, and copy from that into the
1874
601
from bzrlib import osutils
604
from StringIO import StringIO
1876
605
# TODO: Maybe a progress bar while streaming the tarball?
1877
606
note("Copying repository content as tarball...")
1878
607
tar_file = self._get_tarball('bz2')
1879
if tar_file is None:
1881
destination = to_bzrdir.create_repository()
1883
609
tar = tarfile.open('repository', fileobj=tar_file,
1885
tmpdir = osutils.mkdtemp()
611
tmpdir = tempfile.mkdtemp()
1887
613
_extract_tar(tar, tmpdir)
1888
tmp_bzrdir = _mod_bzrdir.BzrDir.open(tmpdir)
614
tmp_bzrdir = BzrDir.open(tmpdir)
1889
615
tmp_repo = tmp_bzrdir.open_repository()
1890
616
tmp_repo.copy_content_into(destination, revision_id)
1892
618
osutils.rmtree(tmpdir)
1894
620
tar_file.close()
621
# TODO: if the server doesn't support this operation, maybe do it the
622
# slow way using the _real_repository?
1896
624
# TODO: Suggestion from john: using external tar is much faster than
1897
625
# python's tarfile library, but it may not work on windows.
1900
def inventories(self):
1901
"""Decorate the real repository for now.
1903
In the long term a full blown network facility is needed to
1904
avoid creating a real repository object locally.
1907
return self._real_repository.inventories
1910
def pack(self, hint=None, clean_obsolete_packs=False):
1911
"""Compress the data within the repository.
1913
This is not currently implemented within the smart server.
1916
return self._real_repository.pack(hint=hint, clean_obsolete_packs=clean_obsolete_packs)
1919
def revisions(self):
1920
"""Decorate the real repository for now.
1922
In the short term this should become a real object to intercept graph
1925
In the long term a full blown network facility is needed.
1928
return self._real_repository.revisions
1930
627
def set_make_working_trees(self, new_value):
1932
new_value_str = "True"
1934
new_value_str = "False"
1935
path = self.bzrdir._path_for_remote_call(self._client)
1937
response = self._call(
1938
'Repository.set_make_working_trees', path, new_value_str)
1939
except errors.UnknownSmartMethod:
1941
self._real_repository.set_make_working_trees(new_value)
1943
if response[0] != 'ok':
1944
raise errors.UnexpectedSmartServerResponse(response)
1947
def signatures(self):
1948
"""Decorate the real repository for now.
1950
In the long term a full blown network facility is needed to avoid
1951
creating a real repository object locally.
1954
return self._real_repository.signatures
628
raise NotImplementedError(self.set_make_working_trees)
1956
630
@needs_write_lock
1957
631
def sign_revision(self, revision_id, gpg_strategy):
1958
632
self._ensure_real()
1959
633
return self._real_repository.sign_revision(revision_id, gpg_strategy)
1963
"""Decorate the real repository for now.
1965
In the long term a full blown network facility is needed to avoid
1966
creating a real repository object locally.
1969
return self._real_repository.texts
1971
635
@needs_read_lock
1972
636
def get_revisions(self, revision_ids):
1973
637
self._ensure_real()
1974
638
return self._real_repository.get_revisions(revision_ids)
1976
640
def supports_rich_root(self):
1977
return self._format.rich_root_data
642
return self._real_repository.supports_rich_root()
1979
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
1980
644
def iter_reverse_revision_history(self, revision_id):
1981
645
self._ensure_real()
1982
646
return self._real_repository.iter_reverse_revision_history(revision_id)
1985
649
def _serializer(self):
1986
return self._format._serializer
651
return self._real_repository._serializer
1988
653
def store_revision_signature(self, gpg_strategy, plaintext, revision_id):
1989
654
self._ensure_real()
1990
655
return self._real_repository.store_revision_signature(
1991
656
gpg_strategy, plaintext, revision_id)
1993
def add_signature_text(self, revision_id, signature):
1995
return self._real_repository.add_signature_text(revision_id, signature)
1997
658
def has_signature_for_revision_id(self, revision_id):
1998
659
self._ensure_real()
1999
660
return self._real_repository.has_signature_for_revision_id(revision_id)
2001
def item_keys_introduced_by(self, revision_ids, _files_pb=None):
2003
return self._real_repository.item_keys_introduced_by(revision_ids,
2004
_files_pb=_files_pb)
2006
def _find_inconsistent_revision_parents(self, revisions_iterator=None):
2008
return self._real_repository._find_inconsistent_revision_parents(
2011
def _check_for_inconsistent_revision_parents(self):
2013
return self._real_repository._check_for_inconsistent_revision_parents()
2015
def _make_parents_provider(self, other=None):
2016
providers = [self._unstacked_provider]
2017
if other is not None:
2018
providers.insert(0, other)
2019
return graph.StackedParentsProvider(_LazyListJoin(
2020
providers, self._fallback_repositories))
2022
def _serialise_search_recipe(self, recipe):
2023
"""Serialise a graph search recipe.
2025
:param recipe: A search recipe (start, stop, count).
2026
:return: Serialised bytes.
2028
start_keys = ' '.join(recipe[1])
2029
stop_keys = ' '.join(recipe[2])
2030
count = str(recipe[3])
2031
return '\n'.join((start_keys, stop_keys, count))
2033
def _serialise_search_result(self, search_result):
2034
parts = search_result.get_network_struct()
2035
return '\n'.join(parts)
2038
path = self.bzrdir._path_for_remote_call(self._client)
2040
response = self._call('PackRepository.autopack', path)
2041
except errors.UnknownSmartMethod:
2043
self._real_repository._pack_collection.autopack()
2046
if response[0] != 'ok':
2047
raise errors.UnexpectedSmartServerResponse(response)
2050
class RemoteStreamSink(vf_repository.StreamSink):
2052
def _insert_real(self, stream, src_format, resume_tokens):
2053
self.target_repo._ensure_real()
2054
sink = self.target_repo._real_repository._get_sink()
2055
result = sink.insert_stream(stream, src_format, resume_tokens)
2057
self.target_repo.autopack()
2060
def insert_stream(self, stream, src_format, resume_tokens):
2061
target = self.target_repo
2062
target._unstacked_provider.missing_keys.clear()
2063
candidate_calls = [('Repository.insert_stream_1.19', (1, 19))]
2064
if target._lock_token:
2065
candidate_calls.append(('Repository.insert_stream_locked', (1, 14)))
2066
lock_args = (target._lock_token or '',)
2068
candidate_calls.append(('Repository.insert_stream', (1, 13)))
2070
client = target._client
2071
medium = client._medium
2072
path = target.bzrdir._path_for_remote_call(client)
2073
# Probe for the verb to use with an empty stream before sending the
2074
# real stream to it. We do this both to avoid the risk of sending a
2075
# large request that is then rejected, and because we don't want to
2076
# implement a way to buffer, rewind, or restart the stream.
2078
for verb, required_version in candidate_calls:
2079
if medium._is_remote_before(required_version):
2082
# We've already done the probing (and set _is_remote_before) on
2083
# a previous insert.
2086
byte_stream = smart_repo._stream_to_byte_stream([], src_format)
2088
response = client.call_with_body_stream(
2089
(verb, path, '') + lock_args, byte_stream)
2090
except errors.UnknownSmartMethod:
2091
medium._remember_remote_is_before(required_version)
2097
return self._insert_real(stream, src_format, resume_tokens)
2098
self._last_inv_record = None
2099
self._last_substream = None
2100
if required_version < (1, 19):
2101
# Remote side doesn't support inventory deltas. Wrap the stream to
2102
# make sure we don't send any. If the stream contains inventory
2103
# deltas we'll interrupt the smart insert_stream request and
2105
stream = self._stop_stream_if_inventory_delta(stream)
2106
byte_stream = smart_repo._stream_to_byte_stream(
2108
resume_tokens = ' '.join(resume_tokens)
2109
response = client.call_with_body_stream(
2110
(verb, path, resume_tokens) + lock_args, byte_stream)
2111
if response[0][0] not in ('ok', 'missing-basis'):
2112
raise errors.UnexpectedSmartServerResponse(response)
2113
if self._last_substream is not None:
2114
# The stream included an inventory-delta record, but the remote
2115
# side isn't new enough to support them. So we need to send the
2116
# rest of the stream via VFS.
2117
self.target_repo.refresh_data()
2118
return self._resume_stream_with_vfs(response, src_format)
2119
if response[0][0] == 'missing-basis':
2120
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2121
resume_tokens = tokens
2122
return resume_tokens, set(missing_keys)
2124
self.target_repo.refresh_data()
2127
def _resume_stream_with_vfs(self, response, src_format):
2128
"""Resume sending a stream via VFS, first resending the record and
2129
substream that couldn't be sent via an insert_stream verb.
2131
if response[0][0] == 'missing-basis':
2132
tokens, missing_keys = bencode.bdecode_as_tuple(response[0][1])
2133
# Ignore missing_keys, we haven't finished inserting yet
2136
def resume_substream():
2137
# Yield the substream that was interrupted.
2138
for record in self._last_substream:
2140
self._last_substream = None
2141
def resume_stream():
2142
# Finish sending the interrupted substream
2143
yield ('inventory-deltas', resume_substream())
2144
# Then simply continue sending the rest of the stream.
2145
for substream_kind, substream in self._last_stream:
2146
yield substream_kind, substream
2147
return self._insert_real(resume_stream(), src_format, tokens)
2149
def _stop_stream_if_inventory_delta(self, stream):
2150
"""Normally this just lets the original stream pass-through unchanged.
2152
However if any 'inventory-deltas' substream occurs it will stop
2153
streaming, and store the interrupted substream and stream in
2154
self._last_substream and self._last_stream so that the stream can be
2155
resumed by _resume_stream_with_vfs.
2158
stream_iter = iter(stream)
2159
for substream_kind, substream in stream_iter:
2160
if substream_kind == 'inventory-deltas':
2161
self._last_substream = substream
2162
self._last_stream = stream_iter
2165
yield substream_kind, substream
2168
class RemoteStreamSource(vf_repository.StreamSource):
2169
"""Stream data from a remote server."""
2171
def get_stream(self, search):
2172
if (self.from_repository._fallback_repositories and
2173
self.to_format._fetch_order == 'topological'):
2174
return self._real_stream(self.from_repository, search)
2177
repos = [self.from_repository]
2183
repos.extend(repo._fallback_repositories)
2184
sources.append(repo)
2185
return self.missing_parents_chain(search, sources)
2187
def get_stream_for_missing_keys(self, missing_keys):
2188
self.from_repository._ensure_real()
2189
real_repo = self.from_repository._real_repository
2190
real_source = real_repo._get_source(self.to_format)
2191
return real_source.get_stream_for_missing_keys(missing_keys)
2193
def _real_stream(self, repo, search):
2194
"""Get a stream for search from repo.
2196
This never called RemoteStreamSource.get_stream, and is a heler
2197
for RemoteStreamSource._get_stream to allow getting a stream
2198
reliably whether fallback back because of old servers or trying
2199
to stream from a non-RemoteRepository (which the stacked support
2202
source = repo._get_source(self.to_format)
2203
if isinstance(source, RemoteStreamSource):
2205
source = repo._real_repository._get_source(self.to_format)
2206
return source.get_stream(search)
2208
def _get_stream(self, repo, search):
2209
"""Core worker to get a stream from repo for search.
2211
This is used by both get_stream and the stacking support logic. It
2212
deliberately gets a stream for repo which does not need to be
2213
self.from_repository. In the event that repo is not Remote, or
2214
cannot do a smart stream, a fallback is made to the generic
2215
repository._get_stream() interface, via self._real_stream.
2217
In the event of stacking, streams from _get_stream will not
2218
contain all the data for search - this is normal (see get_stream).
2220
:param repo: A repository.
2221
:param search: A search.
2223
# Fallbacks may be non-smart
2224
if not isinstance(repo, RemoteRepository):
2225
return self._real_stream(repo, search)
2226
client = repo._client
2227
medium = client._medium
2228
path = repo.bzrdir._path_for_remote_call(client)
2229
search_bytes = repo._serialise_search_result(search)
2230
args = (path, self.to_format.network_name())
2232
('Repository.get_stream_1.19', (1, 19)),
2233
('Repository.get_stream', (1, 13))]
2236
for verb, version in candidate_verbs:
2237
if medium._is_remote_before(version):
2240
response = repo._call_with_body_bytes_expecting_body(
2241
verb, args, search_bytes)
2242
except errors.UnknownSmartMethod:
2243
medium._remember_remote_is_before(version)
2244
except errors.UnknownErrorFromSmartServer, e:
2245
if isinstance(search, graph.EverythingResult):
2246
error_verb = e.error_from_smart_server.error_verb
2247
if error_verb == 'BadSearch':
2248
# Pre-2.4 servers don't support this sort of search.
2249
# XXX: perhaps falling back to VFS on BadSearch is a
2250
# good idea in general? It might provide a little bit
2251
# of protection against client-side bugs.
2252
medium._remember_remote_is_before((2, 4))
2256
response_tuple, response_handler = response
2260
return self._real_stream(repo, search)
2261
if response_tuple[0] != 'ok':
2262
raise errors.UnexpectedSmartServerResponse(response_tuple)
2263
byte_stream = response_handler.read_streamed_body()
2264
src_format, stream = smart_repo._byte_stream_to_stream(byte_stream,
2265
self._record_counter)
2266
if src_format.network_name() != repo._format.network_name():
2267
raise AssertionError(
2268
"Mismatched RemoteRepository and stream src %r, %r" % (
2269
src_format.network_name(), repo._format.network_name()))
2272
def missing_parents_chain(self, search, sources):
2273
"""Chain multiple streams together to handle stacking.
2275
:param search: The overall search to satisfy with streams.
2276
:param sources: A list of Repository objects to query.
2278
self.from_serialiser = self.from_repository._format._serializer
2279
self.seen_revs = set()
2280
self.referenced_revs = set()
2281
# If there are heads in the search, or the key count is > 0, we are not
2283
while not search.is_empty() and len(sources) > 1:
2284
source = sources.pop(0)
2285
stream = self._get_stream(source, search)
2286
for kind, substream in stream:
2287
if kind != 'revisions':
2288
yield kind, substream
2290
yield kind, self.missing_parents_rev_handler(substream)
2291
search = search.refine(self.seen_revs, self.referenced_revs)
2292
self.seen_revs = set()
2293
self.referenced_revs = set()
2294
if not search.is_empty():
2295
for kind, stream in self._get_stream(sources[0], search):
2298
def missing_parents_rev_handler(self, substream):
2299
for content in substream:
2300
revision_bytes = content.get_bytes_as('fulltext')
2301
revision = self.from_serialiser.read_revision_from_string(
2303
self.seen_revs.add(content.key[-1])
2304
self.referenced_revs.update(revision.parent_ids)
2308
663
class RemoteBranchLockableFiles(LockableFiles):
2309
664
"""A 'LockableFiles' implementation that talks to a smart server.
2311
666
This is not a public interface class.
2324
679
self._dir_mode = None
2325
680
self._file_mode = None
683
"""'get' a remote path as per the LockableFiles interface.
685
:param path: the file to 'get'. If this is 'branch.conf', we do not
686
just retrieve a file, instead we ask the smart server to generate
687
a configuration for us - which is retrieved as an INI file.
689
if path == 'branch.conf':
690
path = self.bzrdir._path_for_remote_call(self._client)
691
response = self._client.call_expecting_body(
692
'Branch.get_config_file', path)
693
assert response[0][0] == 'ok', \
694
'unexpected response code %s' % (response[0],)
695
return StringIO(response[1].read_body_bytes())
698
return LockableFiles.get(self, path)
2328
701
class RemoteBranchFormat(branch.BranchFormat):
2330
def __init__(self, network_name=None):
2331
super(RemoteBranchFormat, self).__init__()
2332
self._matchingbzrdir = RemoteBzrDirFormat()
2333
self._matchingbzrdir.set_branch_format(self)
2334
self._custom_format = None
2335
self._network_name = network_name
2337
703
def __eq__(self, other):
2338
return (isinstance(other, RemoteBranchFormat) and
704
return (isinstance(other, RemoteBranchFormat) and
2339
705
self.__dict__ == other.__dict__)
2341
def _ensure_real(self):
2342
if self._custom_format is None:
2343
self._custom_format = branch.network_format_registry.get(
2346
707
def get_format_description(self):
2348
return 'Remote: ' + self._custom_format.get_format_description()
2350
def network_name(self):
2351
return self._network_name
2353
def open(self, a_bzrdir, name=None, ignore_fallbacks=False):
2354
return a_bzrdir.open_branch(name=name,
2355
ignore_fallbacks=ignore_fallbacks)
2357
def _vfs_initialize(self, a_bzrdir, name):
2358
# Initialisation when using a local bzrdir object, or a non-vfs init
2359
# method is not available on the server.
2360
# self._custom_format is always set - the start of initialize ensures
2362
if isinstance(a_bzrdir, RemoteBzrDir):
2363
a_bzrdir._ensure_real()
2364
result = self._custom_format.initialize(a_bzrdir._real_bzrdir,
2367
# We assume the bzrdir is parameterised; it may not be.
2368
result = self._custom_format.initialize(a_bzrdir, name)
2369
if (isinstance(a_bzrdir, RemoteBzrDir) and
2370
not isinstance(result, RemoteBranch)):
2371
result = RemoteBranch(a_bzrdir, a_bzrdir.find_repository(), result,
2375
def initialize(self, a_bzrdir, name=None, repository=None):
2376
# 1) get the network name to use.
2377
if self._custom_format:
2378
network_name = self._custom_format.network_name()
2380
# Select the current bzrlib default and ask for that.
2381
reference_bzrdir_format = _mod_bzrdir.format_registry.get('default')()
2382
reference_format = reference_bzrdir_format.get_branch_format()
2383
self._custom_format = reference_format
2384
network_name = reference_format.network_name()
2385
# Being asked to create on a non RemoteBzrDir:
2386
if not isinstance(a_bzrdir, RemoteBzrDir):
2387
return self._vfs_initialize(a_bzrdir, name=name)
2388
medium = a_bzrdir._client._medium
2389
if medium._is_remote_before((1, 13)):
2390
return self._vfs_initialize(a_bzrdir, name=name)
2391
# Creating on a remote bzr dir.
2392
# 2) try direct creation via RPC
2393
path = a_bzrdir._path_for_remote_call(a_bzrdir._client)
2394
if name is not None:
2395
# XXX JRV20100304: Support creating colocated branches
2396
raise errors.NoColocatedBranchSupport(self)
2397
verb = 'BzrDir.create_branch'
2399
response = a_bzrdir._call(verb, path, network_name)
2400
except errors.UnknownSmartMethod:
2401
# Fallback - use vfs methods
2402
medium._remember_remote_is_before((1, 13))
2403
return self._vfs_initialize(a_bzrdir, name=name)
2404
if response[0] != 'ok':
2405
raise errors.UnexpectedSmartServerResponse(response)
2406
# Turn the response into a RemoteRepository object.
2407
format = RemoteBranchFormat(network_name=response[1])
2408
repo_format = response_tuple_to_repo_format(response[3:])
2409
repo_path = response[2]
2410
if repository is not None:
2411
remote_repo_url = urlutils.join(a_bzrdir.user_url, repo_path)
2412
url_diff = urlutils.relative_url(repository.user_url,
2415
raise AssertionError(
2416
'repository.user_url %r does not match URL from server '
2417
'response (%r + %r)'
2418
% (repository.user_url, a_bzrdir.user_url, repo_path))
2419
remote_repo = repository
2422
repo_bzrdir = a_bzrdir
2424
repo_bzrdir = RemoteBzrDir(
2425
a_bzrdir.root_transport.clone(repo_path), a_bzrdir._format,
2427
remote_repo = RemoteRepository(repo_bzrdir, repo_format)
2428
remote_branch = RemoteBranch(a_bzrdir, remote_repo,
2429
format=format, setup_stacking=False, name=name)
2430
# XXX: We know this is a new branch, so it must have revno 0, revid
2431
# NULL_REVISION. Creating the branch locked would make this be unable
2432
# to be wrong; here its simply very unlikely to be wrong. RBC 20090225
2433
remote_branch._last_revision_info_cache = 0, NULL_REVISION
2434
return remote_branch
2436
def make_tags(self, branch):
2438
return self._custom_format.make_tags(branch)
2440
def supports_tags(self):
2441
# Remote branches might support tags, but we won't know until we
2442
# access the real remote branch.
2444
return self._custom_format.supports_tags()
2446
def supports_stacking(self):
2448
return self._custom_format.supports_stacking()
2450
def supports_set_append_revisions_only(self):
2452
return self._custom_format.supports_set_append_revisions_only()
2454
def _use_default_local_heads_to_fetch(self):
2455
# If the branch format is a metadir format *and* its heads_to_fetch
2456
# implementation is not overridden vs the base class, we can use the
2457
# base class logic rather than use the heads_to_fetch RPC. This is
2458
# usually cheaper in terms of net round trips, as the last-revision and
2459
# tags info fetched is cached and would be fetched anyway.
2461
if isinstance(self._custom_format, branch.BranchFormatMetadir):
2462
branch_class = self._custom_format._branch_class()
2463
heads_to_fetch_impl = branch_class.heads_to_fetch.im_func
2464
if heads_to_fetch_impl is branch.Branch.heads_to_fetch.im_func:
2468
class RemoteBranch(branch.Branch, _RpcHelper, lock._RelockDebugMixin):
708
return 'Remote BZR Branch'
710
def get_format_string(self):
711
return 'Remote BZR Branch'
713
def open(self, a_bzrdir):
714
assert isinstance(a_bzrdir, RemoteBzrDir)
715
return a_bzrdir.open_branch()
717
def initialize(self, a_bzrdir):
718
assert isinstance(a_bzrdir, RemoteBzrDir)
719
return a_bzrdir.create_branch()
722
class RemoteBranch(branch.Branch):
2469
723
"""Branch stored on a server accessed by HPSS RPC.
2471
725
At the moment most operations are mapped down to simple file operations.
2474
728
def __init__(self, remote_bzrdir, remote_repository, real_branch=None,
2475
_client=None, format=None, setup_stacking=True, name=None):
2476
730
"""Create a RemoteBranch instance.
2478
732
:param real_branch: An optional local implementation of the branch
2479
733
format, usually accessing the data via the VFS.
2480
734
:param _client: Private parameter for testing.
2481
:param format: A RemoteBranchFormat object, None to create one
2482
automatically. If supplied it should have a network_name already
2484
:param setup_stacking: If True make an RPC call to determine the
2485
stacked (or not) status of the branch. If False assume the branch
2487
:param name: Colocated branch name
2489
736
# We intentionally don't call the parent class's __init__, because it
2490
737
# will try to assign to self.tags, which is a property in this subclass.
2491
738
# And the parent's __init__ doesn't do much anyway.
739
self._revision_history_cache = None
2492
740
self.bzrdir = remote_bzrdir
2493
741
if _client is not None:
2494
742
self._client = _client
2496
self._client = remote_bzrdir._client
744
self._client = client._SmartClient(self.bzrdir._medium)
2497
745
self.repository = remote_repository
2498
746
if real_branch is not None:
2499
747
self._real_branch = real_branch
2637
809
self._ensure_real()
2638
810
return self._real_branch.get_physical_lock_status()
2640
def get_stacked_on_url(self):
2641
"""Get the URL this branch is stacked against.
2643
:raises NotStacked: If the branch is not stacked.
2644
:raises UnstackableBranchFormat: If the branch does not support
2646
:raises UnstackableRepositoryFormat: If the repository does not support
2650
# there may not be a repository yet, so we can't use
2651
# self._translate_error, so we can't use self._call either.
2652
response = self._client.call('Branch.get_stacked_on_url',
2653
self._remote_path())
2654
except errors.ErrorFromSmartServer, err:
2655
# there may not be a repository yet, so we can't call through
2656
# its _translate_error
2657
_translate_error(err, branch=self)
2658
except errors.UnknownSmartMethod, err:
2660
return self._real_branch.get_stacked_on_url()
2661
if response[0] != 'ok':
2662
raise errors.UnexpectedSmartServerResponse(response)
2665
def set_stacked_on_url(self, url):
2666
branch.Branch.set_stacked_on_url(self, url)
2668
self._is_stacked = False
2670
self._is_stacked = True
2672
def _vfs_get_tags_bytes(self):
2674
return self._real_branch._get_tags_bytes()
2677
def _get_tags_bytes(self):
2678
if self._tags_bytes is None:
2679
self._tags_bytes = self._get_tags_bytes_via_hpss()
2680
return self._tags_bytes
2682
def _get_tags_bytes_via_hpss(self):
2683
medium = self._client._medium
2684
if medium._is_remote_before((1, 13)):
2685
return self._vfs_get_tags_bytes()
2687
response = self._call('Branch.get_tags_bytes', self._remote_path())
2688
except errors.UnknownSmartMethod:
2689
medium._remember_remote_is_before((1, 13))
2690
return self._vfs_get_tags_bytes()
2693
def _vfs_set_tags_bytes(self, bytes):
2695
return self._real_branch._set_tags_bytes(bytes)
2697
def _set_tags_bytes(self, bytes):
2698
if self.is_locked():
2699
self._tags_bytes = bytes
2700
medium = self._client._medium
2701
if medium._is_remote_before((1, 18)):
2702
self._vfs_set_tags_bytes(bytes)
2706
self._remote_path(), self._lock_token, self._repo_lock_token)
2707
response = self._call_with_body_bytes(
2708
'Branch.set_tags_bytes', args, bytes)
2709
except errors.UnknownSmartMethod:
2710
medium._remember_remote_is_before((1, 18))
2711
self._vfs_set_tags_bytes(bytes)
2713
812
def lock_read(self):
2714
"""Lock the branch for read operations.
2716
:return: A bzrlib.lock.LogicalLockResult.
2718
self.repository.lock_read()
2719
813
if not self._lock_mode:
2720
self._note_lock('r')
2721
814
self._lock_mode = 'r'
2722
815
self._lock_count = 1
2723
816
if self._real_branch is not None:
2724
817
self._real_branch.lock_read()
2726
819
self._lock_count += 1
2727
return lock.LogicalLockResult(self.unlock)
2729
821
def _remote_lock_write(self, token):
2730
822
if token is None:
2731
823
branch_token = repo_token = ''
2733
825
branch_token = token
2734
repo_token = self.repository.lock_write().repository_token
826
repo_token = self.repository.lock_write()
2735
827
self.repository.unlock()
2736
err_context = {'token': token}
2738
response = self._call(
2739
'Branch.lock_write', self._remote_path(), branch_token,
2740
repo_token or '', **err_context)
2741
except errors.LockContention, e:
2742
# The LockContention from the server doesn't have any
2743
# information about the lock_url. We re-raise LockContention
2744
# with valid lock_url.
2745
raise errors.LockContention('(remote lock)',
2746
self.repository.base.split('.bzr/')[0])
2747
if response[0] != 'ok':
2748
raise errors.UnexpectedSmartServerResponse(response)
2749
ok, branch_token, repo_token = response
2750
return branch_token, repo_token
828
path = self.bzrdir._path_for_remote_call(self._client)
829
response = self._client.call('Branch.lock_write', path, branch_token,
831
if response[0] == 'ok':
832
ok, branch_token, repo_token = response
833
return branch_token, repo_token
834
elif response[0] == 'LockContention':
835
raise errors.LockContention('(remote lock)')
836
elif response[0] == 'TokenMismatch':
837
raise errors.TokenMismatch(token, '(remote token)')
838
elif response[0] == 'UnlockableTransport':
839
raise errors.UnlockableTransport(self.bzrdir.root_transport)
840
elif response[0] == 'ReadOnlyError':
841
raise errors.ReadOnlyError(self)
843
assert False, 'unexpected response code %r' % (response,)
2752
845
def lock_write(self, token=None):
2753
846
if not self._lock_mode:
2754
self._note_lock('w')
2755
# Lock the branch and repo in one remote call.
2756
847
remote_tokens = self._remote_lock_write(token)
2757
848
self._lock_token, self._repo_lock_token = remote_tokens
2758
if not self._lock_token:
2759
raise SmartProtocolError('Remote server did not return a token!')
2760
# Tell the self.repository object that it is locked.
2761
self.repository.lock_write(
2762
self._repo_lock_token, _skip_rpc=True)
849
assert self._lock_token, 'Remote server did not return a token!'
850
# TODO: We really, really, really don't want to call _ensure_real
851
# here, but it's the easiest way to ensure coherency between the
852
# state of the RemoteBranch and RemoteRepository objects and the
853
# physical locks. If we don't materialise the real objects here,
854
# then getting everything in the right state later is complex, so
855
# for now we just do it the lazy way.
856
# -- Andrew Bennetts, 2007-02-22.
2764
858
if self._real_branch is not None:
2765
self._real_branch.lock_write(token=self._lock_token)
859
self._real_branch.repository.lock_write(
860
token=self._repo_lock_token)
862
self._real_branch.lock_write(token=self._lock_token)
864
self._real_branch.repository.unlock()
2766
865
if token is not None:
2767
866
self._leave_lock = True
868
# XXX: this case seems to be unreachable; token cannot be None.
2769
869
self._leave_lock = False
2770
870
self._lock_mode = 'w'
2771
871
self._lock_count = 1
2772
872
elif self._lock_mode == 'r':
2773
raise errors.ReadOnlyError(self)
873
raise errors.ReadOnlyTransaction
2775
875
if token is not None:
2776
# A token was given to lock_write, and we're relocking, so
2777
# check that the given token actually matches the one we
876
# A token was given to lock_write, and we're relocking, so check
877
# that the given token actually matches the one we already have.
2779
878
if token != self._lock_token:
2780
879
raise errors.TokenMismatch(token, self._lock_token)
2781
880
self._lock_count += 1
2782
# Re-lock the repository too.
2783
self.repository.lock_write(self._repo_lock_token)
2784
return BranchWriteLockResult(self.unlock, self._lock_token or None)
881
return self._lock_token
2786
883
def _unlock(self, branch_token, repo_token):
2787
err_context = {'token': str((branch_token, repo_token))}
2788
response = self._call(
2789
'Branch.unlock', self._remote_path(), branch_token,
2790
repo_token or '', **err_context)
884
path = self.bzrdir._path_for_remote_call(self._client)
885
response = self._client.call('Branch.unlock', path, branch_token,
2791
887
if response == ('ok',):
2793
raise errors.UnexpectedSmartServerResponse(response)
889
elif response[0] == 'TokenMismatch':
890
raise errors.TokenMismatch(
891
str((branch_token, repo_token)), '(remote tokens)')
893
assert False, 'unexpected response code %s' % (response,)
2795
@only_raises(errors.LockNotHeld, errors.LockBroken)
2796
895
def unlock(self):
2798
self._lock_count -= 1
2799
if not self._lock_count:
2800
self._clear_cached_state()
2801
mode = self._lock_mode
2802
self._lock_mode = None
2803
if self._real_branch is not None:
2804
if (not self._leave_lock and mode == 'w' and
2805
self._repo_lock_token):
2806
# If this RemoteBranch will remove the physical lock
2807
# for the repository, make sure the _real_branch
2808
# doesn't do it first. (Because the _real_branch's
2809
# repository is set to be the RemoteRepository.)
2810
self._real_branch.repository.leave_lock_in_place()
2811
self._real_branch.unlock()
2813
# Only write-locked branched need to make a remote method
2814
# call to perform the unlock.
2816
if not self._lock_token:
2817
raise AssertionError('Locked, but no token!')
2818
branch_token = self._lock_token
2819
repo_token = self._repo_lock_token
2820
self._lock_token = None
2821
self._repo_lock_token = None
896
self._lock_count -= 1
897
if not self._lock_count:
898
self._clear_cached_state()
899
mode = self._lock_mode
900
self._lock_mode = None
901
if self._real_branch is not None:
2822
902
if not self._leave_lock:
2823
self._unlock(branch_token, repo_token)
2825
self.repository.unlock()
903
# If this RemoteBranch will remove the physical lock for the
904
# repository, make sure the _real_branch doesn't do it
905
# first. (Because the _real_branch's repository is set to
906
# be the RemoteRepository.)
907
self._real_branch.repository.leave_lock_in_place()
908
self._real_branch.unlock()
910
# Only write-locked branched need to make a remote method call
911
# to perfom the unlock.
913
assert self._lock_token, 'Locked, but no token!'
914
branch_token = self._lock_token
915
repo_token = self._repo_lock_token
916
self._lock_token = None
917
self._repo_lock_token = None
918
if not self._leave_lock:
919
self._unlock(branch_token, repo_token)
2827
921
def break_lock(self):
2828
922
self._ensure_real()
2829
923
return self._real_branch.break_lock()
2831
925
def leave_lock_in_place(self):
2832
if not self._lock_token:
2833
raise NotImplementedError(self.leave_lock_in_place)
2834
926
self._leave_lock = True
2836
928
def dont_leave_lock_in_place(self):
2837
if not self._lock_token:
2838
raise NotImplementedError(self.dont_leave_lock_in_place)
2839
929
self._leave_lock = False
2842
def get_rev_id(self, revno, history=None):
2844
return _mod_revision.NULL_REVISION
2845
last_revision_info = self.last_revision_info()
2846
ok, result = self.repository.get_rev_id_for_revno(
2847
revno, last_revision_info)
2850
missing_parent = result[1]
2851
# Either the revision named by the server is missing, or its parent
2852
# is. Call get_parent_map to determine which, so that we report a
2854
parent_map = self.repository.get_parent_map([missing_parent])
2855
if missing_parent in parent_map:
2856
missing_parent = parent_map[missing_parent]
2857
raise errors.RevisionNotPresent(missing_parent, self.repository)
2859
def _read_last_revision_info(self):
2860
response = self._call('Branch.last_revision_info', self._remote_path())
2861
if response[0] != 'ok':
2862
raise SmartProtocolError('unexpected response code %s' % (response,))
931
def last_revision_info(self):
932
"""See Branch.last_revision_info()."""
933
path = self.bzrdir._path_for_remote_call(self._client)
934
response = self._client.call('Branch.last_revision_info', path)
935
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
2863
936
revno = int(response[1])
2864
937
last_revision = response[2]
2865
938
return (revno, last_revision)
2867
940
def _gen_revision_history(self):
2868
941
"""See Branch._gen_revision_history()."""
2869
if self._is_stacked:
2871
return self._real_branch._gen_revision_history()
2872
response_tuple, response_handler = self._call_expecting_body(
2873
'Branch.revision_history', self._remote_path())
2874
if response_tuple[0] != 'ok':
2875
raise errors.UnexpectedSmartServerResponse(response_tuple)
2876
result = response_handler.read_body_bytes().split('\x00')
942
path = self.bzrdir._path_for_remote_call(self._client)
943
response = self._client.call_expecting_body(
944
'Branch.revision_history', path)
945
assert response[0][0] == 'ok', ('unexpected response code %s'
947
result = response[1].read_body_bytes().split('\x00')
2877
948
if result == ['']:
2881
def _remote_path(self):
2882
return self.bzrdir._path_for_remote_call(self._client)
2884
def _set_last_revision_descendant(self, revision_id, other_branch,
2885
allow_diverged=False, allow_overwrite_descendant=False):
2886
# This performs additional work to meet the hook contract; while its
2887
# undesirable, we have to synthesise the revno to call the hook, and
2888
# not calling the hook is worse as it means changes can't be prevented.
2889
# Having calculated this though, we can't just call into
2890
# set_last_revision_info as a simple call, because there is a set_rh
2891
# hook that some folk may still be using.
2892
old_revno, old_revid = self.last_revision_info()
2893
history = self._lefthand_history(revision_id)
2894
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2895
err_context = {'other_branch': other_branch}
2896
response = self._call('Branch.set_last_revision_ex',
2897
self._remote_path(), self._lock_token, self._repo_lock_token,
2898
revision_id, int(allow_diverged), int(allow_overwrite_descendant),
2900
self._clear_cached_state()
2901
if len(response) != 3 and response[0] != 'ok':
2902
raise errors.UnexpectedSmartServerResponse(response)
2903
new_revno, new_revision_id = response[1:]
2904
self._last_revision_info_cache = new_revno, new_revision_id
2905
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2906
if self._real_branch is not None:
2907
cache = new_revno, new_revision_id
2908
self._real_branch._last_revision_info_cache = cache
2910
def _set_last_revision(self, revision_id):
2911
old_revno, old_revid = self.last_revision_info()
2912
# This performs additional work to meet the hook contract; while its
2913
# undesirable, we have to synthesise the revno to call the hook, and
2914
# not calling the hook is worse as it means changes can't be prevented.
2915
# Having calculated this though, we can't just call into
2916
# set_last_revision_info as a simple call, because there is a set_rh
2917
# hook that some folk may still be using.
2918
history = self._lefthand_history(revision_id)
2919
self._run_pre_change_branch_tip_hooks(len(history), revision_id)
2920
self._clear_cached_state()
2921
response = self._call('Branch.set_last_revision',
2922
self._remote_path(), self._lock_token, self._repo_lock_token,
2924
if response != ('ok',):
2925
raise errors.UnexpectedSmartServerResponse(response)
2926
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
2928
@symbol_versioning.deprecated_method(symbol_versioning.deprecated_in((2, 4, 0)))
2929
952
@needs_write_lock
2930
953
def set_revision_history(self, rev_history):
2931
"""See Branch.set_revision_history."""
2932
self._set_revision_history(rev_history)
2935
def _set_revision_history(self, rev_history):
2936
954
# Send just the tip revision of the history; the server will generate
2937
955
# the full history from that. If the revision doesn't exist in this
2938
956
# branch, NoSuchRevision will be raised.
957
path = self.bzrdir._path_for_remote_call(self._client)
2939
958
if rev_history == []:
2940
959
rev_id = 'null:'
2942
961
rev_id = rev_history[-1]
2943
self._set_last_revision(rev_id)
2944
for hook in branch.Branch.hooks['set_rh']:
2945
hook(self, rev_history)
962
self._clear_cached_state()
963
response = self._client.call('Branch.set_last_revision',
964
path, self._lock_token, self._repo_lock_token, rev_id)
965
if response[0] == 'NoSuchRevision':
966
raise NoSuchRevision(self, rev_id)
968
assert response == ('ok',), (
969
'unexpected response code %r' % (response,))
2946
970
self._cache_revision_history(rev_history)
2948
def _get_parent_location(self):
2949
medium = self._client._medium
2950
if medium._is_remote_before((1, 13)):
2951
return self._vfs_get_parent_location()
2953
response = self._call('Branch.get_parent', self._remote_path())
2954
except errors.UnknownSmartMethod:
2955
medium._remember_remote_is_before((1, 13))
2956
return self._vfs_get_parent_location()
2957
if len(response) != 1:
2958
raise errors.UnexpectedSmartServerResponse(response)
2959
parent_location = response[0]
2960
if parent_location == '':
2962
return parent_location
2964
def _vfs_get_parent_location(self):
2966
return self._real_branch._get_parent_location()
2968
def _set_parent_location(self, url):
2969
medium = self._client._medium
2970
if medium._is_remote_before((1, 15)):
2971
return self._vfs_set_parent_location(url)
2973
call_url = url or ''
2974
if type(call_url) is not str:
2975
raise AssertionError('url must be a str or None (%s)' % url)
2976
response = self._call('Branch.set_parent_location',
2977
self._remote_path(), self._lock_token, self._repo_lock_token,
2979
except errors.UnknownSmartMethod:
2980
medium._remember_remote_is_before((1, 15))
2981
return self._vfs_set_parent_location(url)
2983
raise errors.UnexpectedSmartServerResponse(response)
2985
def _vfs_set_parent_location(self, url):
2987
return self._real_branch._set_parent_location(url)
972
def get_parent(self):
974
return self._real_branch.get_parent()
976
def set_parent(self, url):
978
return self._real_branch.set_parent(url)
980
def get_config(self):
981
return RemoteBranchConfig(self)
983
def sprout(self, to_bzrdir, revision_id=None):
984
# Like Branch.sprout, except that it sprouts a branch in the default
985
# format, because RemoteBranches can't be created at arbitrary URLs.
986
# XXX: if to_bzrdir is a RemoteBranch, this should perhaps do
987
# to_bzrdir.create_branch...
988
result = branch.BranchFormat.get_default_format().initialize(to_bzrdir)
989
self.copy_content_into(result, revision_id=revision_id)
990
result.set_parent(self.bzrdir.root_transport.base)
994
def append_revision(self, *revision_ids):
996
return self._real_branch.append_revision(*revision_ids)
2989
998
@needs_write_lock
2990
999
def pull(self, source, overwrite=False, stop_revision=None,
2992
self._clear_cached_state_of_remote_branch_only()
1001
# FIXME: This asks the real branch to run the hooks, which means
1002
# they're called with the wrong target branch parameter.
1003
# The test suite specifically allows this at present but it should be
1004
# fixed. It should get a _override_hook_target branch,
1005
# as push does. -- mbp 20070405
2993
1006
self._ensure_real()
2994
return self._real_branch.pull(
1007
self._real_branch.pull(
2995
1008
source, overwrite=overwrite, stop_revision=stop_revision,
2996
_override_hook_target=self, **kwargs)
2998
1011
@needs_read_lock
2999
def push(self, target, overwrite=False, stop_revision=None, lossy=False):
1012
def push(self, target, overwrite=False, stop_revision=None):
3000
1013
self._ensure_real()
3001
1014
return self._real_branch.push(
3002
target, overwrite=overwrite, stop_revision=stop_revision, lossy=lossy,
1015
target, overwrite=overwrite, stop_revision=stop_revision,
3003
1016
_override_hook_source_branch=self)
3005
1018
def is_locked(self):
3006
1019
return self._lock_count >= 1
3009
def revision_id_to_revno(self, revision_id):
3011
return self._real_branch.revision_id_to_revno(revision_id)
3014
1021
def set_last_revision_info(self, revno, revision_id):
3015
# XXX: These should be returned by the set_last_revision_info verb
3016
old_revno, old_revid = self.last_revision_info()
3017
self._run_pre_change_branch_tip_hooks(revno, revision_id)
3018
if not revision_id or not isinstance(revision_id, basestring):
3019
raise errors.InvalidRevisionId(revision_id=revision_id, branch=self)
3021
response = self._call('Branch.set_last_revision_info',
3022
self._remote_path(), self._lock_token, self._repo_lock_token,
3023
str(revno), revision_id)
3024
except errors.UnknownSmartMethod:
3026
self._clear_cached_state_of_remote_branch_only()
3027
self._real_branch.set_last_revision_info(revno, revision_id)
3028
self._last_revision_info_cache = revno, revision_id
3030
if response == ('ok',):
3031
self._clear_cached_state()
3032
self._last_revision_info_cache = revno, revision_id
3033
self._run_post_change_branch_tip_hooks(old_revno, old_revid)
3034
# Update the _real_branch's cache too.
3035
if self._real_branch is not None:
3036
cache = self._last_revision_info_cache
3037
self._real_branch._last_revision_info_cache = cache
3039
raise errors.UnexpectedSmartServerResponse(response)
1023
self._clear_cached_state()
1024
return self._real_branch.set_last_revision_info(revno, revision_id)
3042
1026
def generate_revision_history(self, revision_id, last_rev=None,
3043
1027
other_branch=None):
3044
medium = self._client._medium
3045
if not medium._is_remote_before((1, 6)):
3046
# Use a smart method for 1.6 and above servers
3048
self._set_last_revision_descendant(revision_id, other_branch,
3049
allow_diverged=True, allow_overwrite_descendant=True)
3051
except errors.UnknownSmartMethod:
3052
medium._remember_remote_is_before((1, 6))
3053
self._clear_cached_state_of_remote_branch_only()
3054
self._set_revision_history(self._lefthand_history(revision_id,
3055
last_rev=last_rev,other_branch=other_branch))
1029
return self._real_branch.generate_revision_history(
1030
revision_id, last_rev=last_rev, other_branch=other_branch)
1035
return self._real_branch.tags
3057
1037
def set_push_location(self, location):
3058
1038
self._ensure_real()
3059
1039
return self._real_branch.set_push_location(location)
3061
def heads_to_fetch(self):
3062
if self._format._use_default_local_heads_to_fetch():
3063
# We recognise this format, and its heads-to-fetch implementation
3064
# is the default one (tip + tags). In this case it's cheaper to
3065
# just use the default implementation rather than a special RPC as
3066
# the tip and tags data is cached.
3067
return branch.Branch.heads_to_fetch(self)
3068
medium = self._client._medium
3069
if medium._is_remote_before((2, 4)):
3070
return self._vfs_heads_to_fetch()
3072
return self._rpc_heads_to_fetch()
3073
except errors.UnknownSmartMethod:
3074
medium._remember_remote_is_before((2, 4))
3075
return self._vfs_heads_to_fetch()
3077
def _rpc_heads_to_fetch(self):
3078
response = self._call('Branch.heads_to_fetch', self._remote_path())
3079
if len(response) != 2:
3080
raise errors.UnexpectedSmartServerResponse(response)
3081
must_fetch, if_present_fetch = response
3082
return set(must_fetch), set(if_present_fetch)
3084
def _vfs_heads_to_fetch(self):
1041
def update_revisions(self, other, stop_revision=None):
3085
1042
self._ensure_real()
3086
return self._real_branch.heads_to_fetch()
3089
class RemoteConfig(object):
3090
"""A Config that reads and writes from smart verbs.
3092
It is a low-level object that considers config data to be name/value pairs
3093
that may be associated with a section. Assigning meaning to the these
3094
values is done at higher levels like bzrlib.config.TreeConfig.
3097
def get_option(self, name, section=None, default=None):
3098
"""Return the value associated with a named option.
3100
:param name: The name of the value
3101
:param section: The section the option is in (if any)
3102
:param default: The value to return if the value is not set
3103
:return: The value or default value
3106
configobj = self._get_configobj()
3109
section_obj = configobj
3112
section_obj = configobj[section]
3115
if section_obj is None:
3118
value = section_obj.get(name, default)
3119
except errors.UnknownSmartMethod:
3120
value = self._vfs_get_option(name, section, default)
3121
for hook in config.OldConfigHooks['get']:
3122
hook(self, name, value)
3125
def _response_to_configobj(self, response):
3126
if len(response[0]) and response[0][0] != 'ok':
3127
raise errors.UnexpectedSmartServerResponse(response)
3128
lines = response[1].read_body_bytes().splitlines()
3129
conf = config.ConfigObj(lines, encoding='utf-8')
3130
for hook in config.OldConfigHooks['load']:
3135
class RemoteBranchConfig(RemoteConfig):
3136
"""A RemoteConfig for Branches."""
3138
def __init__(self, branch):
3139
self._branch = branch
3141
def _get_configobj(self):
3142
path = self._branch._remote_path()
3143
response = self._branch._client.call_expecting_body(
3144
'Branch.get_config_file', path)
3145
return self._response_to_configobj(response)
3147
def set_option(self, value, name, section=None):
3148
"""Set the value associated with a named option.
3150
:param value: The value to set
3151
:param name: The name of the value to set
3152
:param section: The section the option is in (if any)
3154
medium = self._branch._client._medium
3155
if medium._is_remote_before((1, 14)):
3156
return self._vfs_set_option(value, name, section)
3157
if isinstance(value, dict):
3158
if medium._is_remote_before((2, 2)):
3159
return self._vfs_set_option(value, name, section)
3160
return self._set_config_option_dict(value, name, section)
3162
return self._set_config_option(value, name, section)
3164
def _set_config_option(self, value, name, section):
3166
path = self._branch._remote_path()
3167
response = self._branch._client.call('Branch.set_config_option',
3168
path, self._branch._lock_token, self._branch._repo_lock_token,
3169
value.encode('utf8'), name, section or '')
3170
except errors.UnknownSmartMethod:
3171
medium = self._branch._client._medium
3172
medium._remember_remote_is_before((1, 14))
3173
return self._vfs_set_option(value, name, section)
3175
raise errors.UnexpectedSmartServerResponse(response)
3177
def _serialize_option_dict(self, option_dict):
3179
for key, value in option_dict.items():
3180
if isinstance(key, unicode):
3181
key = key.encode('utf8')
3182
if isinstance(value, unicode):
3183
value = value.encode('utf8')
3184
utf8_dict[key] = value
3185
return bencode.bencode(utf8_dict)
3187
def _set_config_option_dict(self, value, name, section):
3189
path = self._branch._remote_path()
3190
serialised_dict = self._serialize_option_dict(value)
3191
response = self._branch._client.call(
3192
'Branch.set_config_option_dict',
3193
path, self._branch._lock_token, self._branch._repo_lock_token,
3194
serialised_dict, name, section or '')
3195
except errors.UnknownSmartMethod:
3196
medium = self._branch._client._medium
3197
medium._remember_remote_is_before((2, 2))
3198
return self._vfs_set_option(value, name, section)
3200
raise errors.UnexpectedSmartServerResponse(response)
3202
def _real_object(self):
3203
self._branch._ensure_real()
3204
return self._branch._real_branch
3206
def _vfs_set_option(self, value, name, section=None):
3207
return self._real_object()._get_config().set_option(
3208
value, name, section)
3211
class RemoteBzrDirConfig(RemoteConfig):
3212
"""A RemoteConfig for BzrDirs."""
3214
def __init__(self, bzrdir):
3215
self._bzrdir = bzrdir
3217
def _get_configobj(self):
3218
medium = self._bzrdir._client._medium
3219
verb = 'BzrDir.get_config_file'
3220
if medium._is_remote_before((1, 15)):
3221
raise errors.UnknownSmartMethod(verb)
3222
path = self._bzrdir._path_for_remote_call(self._bzrdir._client)
3223
response = self._bzrdir._call_expecting_body(
3225
return self._response_to_configobj(response)
3227
def _vfs_get_option(self, name, section, default):
3228
return self._real_object()._get_config().get_option(
3229
name, section, default)
3231
def set_option(self, value, name, section=None):
3232
"""Set the value associated with a named option.
3234
:param value: The value to set
3235
:param name: The name of the value to set
3236
:param section: The section the option is in (if any)
3238
return self._real_object()._get_config().set_option(
3239
value, name, section)
3241
def _real_object(self):
3242
self._bzrdir._ensure_real()
3243
return self._bzrdir._real_bzrdir
1043
return self._real_branch.update_revisions(
1044
other, stop_revision=stop_revision)
1047
class RemoteBranchConfig(BranchConfig):
1050
self.branch._ensure_real()
1051
return self.branch._real_branch.get_config().username()
1053
def _get_branch_data_config(self):
1054
self.branch._ensure_real()
1055
if self._branch_data_config is None:
1056
self._branch_data_config = TreeConfig(self.branch._real_branch)
1057
return self._branch_data_config
3247
1060
def _extract_tar(tar, to_dir):