34
34
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
35
35
from bzrlib.config import BranchConfig, TreeConfig
36
36
from bzrlib.decorators import needs_read_lock, needs_write_lock
37
from bzrlib.errors import NoSuchRevision
37
from bzrlib.errors import (
38
41
from bzrlib.lockable_files import LockableFiles
39
42
from bzrlib.pack import ContainerPushParser
40
43
from bzrlib.smart import client, vfs
41
44
from bzrlib.symbol_versioning import (
45
from bzrlib.revision import NULL_REVISION
48
from bzrlib.revision import ensure_null, NULL_REVISION
46
49
from bzrlib.trace import mutter, note, warning
48
51
# Note: RemoteBzrDirFormat is in bzrdir.py
118
121
def get_branch_reference(self):
119
122
"""See BzrDir.get_branch_reference()."""
120
123
path = self._path_for_remote_call(self._client)
121
response = self._client.call('BzrDir.open_branch', path)
125
response = self._client.call('BzrDir.open_branch', path)
126
except errors.ErrorFromSmartServer, err:
127
if err.error_tuple == ('nobranch',):
128
raise errors.NotBranchError(path=self.root_transport.base)
122
130
if response[0] == 'ok':
123
131
if response[1] == '':
124
132
# branch at this location.
136
142
return None, self.open_branch()
138
144
def open_branch(self, _unsupported=False):
139
assert _unsupported == False, 'unsupported flag support not implemented yet.'
146
raise NotImplementedError('unsupported flag support not implemented yet.')
140
147
reference_url = self.get_branch_reference()
141
148
if reference_url is None:
142
149
# branch at this location.
149
156
def open_repository(self):
150
157
path = self._path_for_remote_call(self._client)
151
158
verb = 'BzrDir.find_repositoryV2'
152
response = self._client.call(verb, path)
153
if (response == ('error', "Generic bzr smart protocol error: "
154
"bad request '%s'" % verb) or
155
response == ('error', "Generic bzr smart protocol error: "
156
"bad request u'%s'" % verb)):
157
verb = 'BzrDir.find_repository'
158
response = self._client.call(verb, path)
159
assert response[0] in ('ok', 'norepository'), \
160
'unexpected response code %s' % (response,)
161
if response[0] == 'norepository':
162
raise errors.NoRepositoryPresent(self)
161
response = self._client.call(verb, path)
162
except errors.UnknownSmartMethod:
163
verb = 'BzrDir.find_repository'
164
response = self._client.call(verb, path)
165
except errors.ErrorFromSmartServer, err:
166
if err.error_verb == 'norepository':
167
raise errors.NoRepositoryPresent(self)
169
if response[0] != 'ok':
170
raise errors.UnexpectedSmartServerResponse(response)
163
171
if verb == 'BzrDir.find_repository':
164
172
# servers that don't support the V2 method don't support external
165
173
# references either.
166
174
response = response + ('no', )
167
assert len(response) == 5, 'incorrect response length %s' % (response,)
175
if not (len(response) == 5):
176
raise SmartProtocolError('incorrect response length %s' % (response,))
168
177
if response[1] == '':
169
178
format = RemoteRepositoryFormat()
170
179
format.rich_root_data = (response[2] == 'yes')
228
237
_matchingbzrdir = RemoteBzrDirFormat
230
239
def initialize(self, a_bzrdir, shared=False):
231
assert isinstance(a_bzrdir, RemoteBzrDir), \
232
'%r is not a RemoteBzrDir' % (a_bzrdir,)
240
if not isinstance(a_bzrdir, RemoteBzrDir):
241
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
233
242
return a_bzrdir.create_repository(shared=shared)
235
244
def open(self, a_bzrdir):
236
assert isinstance(a_bzrdir, RemoteBzrDir)
245
if not isinstance(a_bzrdir, RemoteBzrDir):
246
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
237
247
return a_bzrdir.open_repository()
239
249
def get_format_description(self):
375
385
path = self.bzrdir._path_for_remote_call(self._client)
376
assert type(revision_id) is str
377
response = self._client.call_expecting_body(
378
'Repository.get_revision_graph', path, revision_id)
379
if response[0][0] not in ['ok', 'nosuchrevision']:
380
raise errors.UnexpectedSmartServerResponse(response[0])
381
if response[0][0] == 'ok':
382
coded = response[1].read_body_bytes()
384
# no revisions in this repository!
386
lines = coded.split('\n')
389
d = tuple(line.split())
390
revision_graph[d[0]] = d[1:]
392
return revision_graph
394
response_body = response[1].read_body_bytes()
395
assert response_body == ''
396
raise NoSuchRevision(self, revision_id)
387
response = self._client.call_expecting_body(
388
'Repository.get_revision_graph', path, revision_id)
389
except errors.ErrorFromSmartServer, err:
390
if err.error_verb == 'nosuchrevision':
391
raise NoSuchRevision(self, revision_id)
393
response_tuple, response_handler = response
394
if response_tuple[0] != 'ok':
395
raise errors.UnexpectedSmartServerResponse(response_tuple)
396
coded = response_handler.read_body_bytes()
398
# no revisions in this repository!
400
lines = coded.split('\n')
403
d = tuple(line.split())
404
revision_graph[d[0]] = d[1:]
406
return revision_graph
398
408
def has_revision(self, revision_id):
399
409
"""See Repository.has_revision()."""
401
411
# The null revision is always present.
403
413
path = self.bzrdir._path_for_remote_call(self._client)
404
response = self._client.call('Repository.has_revision', path, revision_id)
405
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
414
response = self._client.call(
415
'Repository.has_revision', path, revision_id)
416
if response[0] not in ('yes', 'no'):
417
raise errors.UnexpectedSmartServerResponse(response)
406
418
return response[0] == 'yes'
408
420
def has_revisions(self, revision_ids):
439
451
fmt_committers = 'no'
441
453
fmt_committers = 'yes'
442
response = self._client.call_expecting_body(
454
response_tuple, response_handler = self._client.call_expecting_body(
443
455
'Repository.gather_stats', path, fmt_revid, fmt_committers)
444
assert response[0][0] == 'ok', \
445
'unexpected response code %s' % (response[0],)
456
if response_tuple[0] != 'ok':
457
raise errors.UnexpectedSmartServerResponse(response_tuple)
447
body = response[1].read_body_bytes()
459
body = response_handler.read_body_bytes()
449
461
for line in body.split('\n'):
485
497
"""See Repository.is_shared()."""
486
498
path = self.bzrdir._path_for_remote_call(self._client)
487
499
response = self._client.call('Repository.is_shared', path)
488
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
500
if response[0] not in ('yes', 'no'):
501
raise SmartProtocolError('unexpected response code %s' % (response,))
489
502
return response[0] == 'yes'
491
504
def is_write_locked(self):
508
521
path = self.bzrdir._path_for_remote_call(self._client)
509
522
if token is None:
511
response = self._client.call('Repository.lock_write', path, token)
525
response = self._client.call('Repository.lock_write', path, token)
526
except errors.ErrorFromSmartServer, err:
527
if err.error_verb == 'LockContention':
528
raise errors.LockContention('(remote lock)')
529
elif err.error_verb == 'UnlockableTransport':
530
raise errors.UnlockableTransport(self.bzrdir.root_transport)
531
elif err.error_verb == 'LockFailed':
532
raise errors.LockFailed(err.error_args[0], err.error_args[1])
512
535
if response[0] == 'ok':
513
536
ok, token = response
515
elif response[0] == 'LockContention':
516
raise errors.LockContention('(remote lock)')
517
elif response[0] == 'UnlockableTransport':
518
raise errors.UnlockableTransport(self.bzrdir.root_transport)
519
elif response[0] == 'LockFailed':
520
raise errors.LockFailed(response[1], response[2])
522
539
raise errors.UnexpectedSmartServerResponse(response)
561
578
:param repository: The repository to fallback to for non-hpss
562
579
implemented operations.
564
assert not isinstance(repository, RemoteRepository)
581
if isinstance(repository, RemoteRepository):
582
raise AssertionError()
565
583
self._real_repository = repository
566
584
if self._lock_mode == 'w':
567
585
# if we are already locked, the real repository must be able to
587
605
# with no token the remote repository is not persistently locked.
589
response = self._client.call('Repository.unlock', path, token)
608
response = self._client.call('Repository.unlock', path, token)
609
except errors.ErrorFromSmartServer, err:
610
if err.error_verb == 'TokenMismatch':
611
raise errors.TokenMismatch(token, '(remote token)')
590
613
if response == ('ok',):
592
elif response[0] == 'TokenMismatch':
593
raise errors.TokenMismatch(token, '(remote token)')
595
616
raise errors.UnexpectedSmartServerResponse(response)
636
657
path = self.bzrdir._path_for_remote_call(self._client)
637
response, protocol = self._client.call_expecting_body(
638
'Repository.tarball', path, compression)
659
response, protocol = self._client.call_expecting_body(
660
'Repository.tarball', path, compression)
661
except errors.UnknownSmartMethod:
662
protocol.cancel_read_body()
639
664
if response[0] == 'ok':
640
665
# Extract the tarball and return it
641
666
t = tempfile.NamedTemporaryFile()
643
668
t.write(protocol.read_body_bytes())
646
if (response == ('error', "Generic bzr smart protocol error: "
647
"bad request 'Repository.tarball'") or
648
response == ('error', "Generic bzr smart protocol error: "
649
"bad request u'Repository.tarball'")):
650
protocol.cancel_read_body()
652
671
raise errors.UnexpectedSmartServerResponse(response)
654
673
def sprout(self, to_bzrdir, revision_id=None):
718
737
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
720
739
def make_working_trees(self):
721
"""RemoteRepositories never create working trees by default."""
740
"""See Repository.make_working_trees"""
742
return self._real_repository.make_working_trees()
724
744
def revision_ids_to_search_result(self, result_set):
725
745
"""Convert a set of revision ids to a graph SearchResult."""
810
830
ancestry.update(parent_map)
811
831
present_keys = [k for k in keys if k in ancestry]
812
832
if 'hpss' in debug.debug_flags:
813
self._requested_parents.update(present_keys)
814
mutter('Current RemoteRepository graph hit rate: %d%%',
815
100.0 * len(self._requested_parents) / len(ancestry))
833
if self._requested_parents is not None and len(ancestry) != 0:
834
self._requested_parents.update(present_keys)
835
mutter('Current RemoteRepository graph hit rate: %d%%',
836
100.0 * len(self._requested_parents) / len(ancestry))
816
837
return dict((k, ancestry[k]) for k in present_keys)
818
def _response_is_unknown_method(self, response, verb):
819
"""Return True if response is an unknonwn method response to verb.
821
:param response: The response from a smart client call_expecting_body
823
:param verb: The verb used in that call.
824
:return: True if an unknown method was encountered.
826
# This might live better on
827
# bzrlib.smart.protocol.SmartClientRequestProtocolOne
828
if (response[0] == ('error', "Generic bzr smart protocol error: "
829
"bad request '%s'" % verb) or
830
response[0] == ('error', "Generic bzr smart protocol error: "
831
"bad request u'%s'" % verb)):
832
response[1].cancel_read_body()
836
839
def _get_parent_map(self, keys):
837
840
"""Helper for get_parent_map that performs the RPC."""
838
841
medium = self._client._medium
844
847
# :- its because we're working with a deprecated server anyway, and
845
848
# the user will almost certainly have seen a warning about the
846
849
# server version already.
847
return self.get_revision_graph()
850
rg = self.get_revision_graph()
851
# There is an api discrepency between get_parent_map and
852
# get_revision_graph. Specifically, a "key:()" pair in
853
# get_revision_graph just means a node has no parents. For
854
# "get_parent_map" it means the node is a ghost. So fix up the
855
# graph to correct this.
856
# https://bugs.launchpad.net/bzr/+bug/214894
857
# There is one other "bug" which is that ghosts in
858
# get_revision_graph() are not returned at all. But we won't worry
859
# about that for now.
860
for node_id, parent_ids in rg.iteritems():
862
rg[node_id] = (NULL_REVISION,)
863
rg[NULL_REVISION] = ()
868
raise ValueError('get_parent_map(None) is not valid')
850
869
if NULL_REVISION in keys:
851
870
keys.discard(NULL_REVISION)
852
871
found_parents = {NULL_REVISION:()}
880
899
body = self._serialise_search_recipe(recipe)
881
900
path = self.bzrdir._path_for_remote_call(self._client)
883
assert type(key) is str
902
if type(key) is not str:
904
"key %r not a plain string" % (key,))
884
905
verb = 'Repository.get_parent_map'
885
906
args = (path,) + tuple(keys)
886
response = self._client.call_with_body_bytes_expecting_body(
887
verb, args, self._serialise_search_recipe(recipe))
888
if self._response_is_unknown_method(response, verb):
908
response = self._client.call_with_body_bytes_expecting_body(
909
verb, args, self._serialise_search_recipe(recipe))
910
except errors.UnknownSmartMethod:
889
911
# Server does not support this method, so get the whole graph.
890
912
# Worse, we have to force a disconnection, because the server now
891
913
# doesn't realise it has a body on the wire to consume, so the
897
919
# To avoid having to disconnect repeatedly, we keep track of the
898
920
# fact the server doesn't understand remote methods added in 1.2.
899
921
medium._remote_is_at_least_1_2 = False
900
return self._get_revision_graph(None)
901
elif response[0][0] not in ['ok']:
902
reponse[1].cancel_read_body()
903
raise errors.UnexpectedSmartServerResponse(response[0])
904
if response[0][0] == 'ok':
905
coded = bz2.decompress(response[1].read_body_bytes())
922
return self.get_revision_graph(None)
923
response_tuple, response_handler = response
924
if response_tuple[0] not in ['ok']:
925
response_handler.cancel_read_body()
926
raise errors.UnexpectedSmartServerResponse(response_tuple)
927
if response_tuple[0] == 'ok':
928
coded = bz2.decompress(response_handler.read_body_bytes())
907
930
# no revisions found
1061
1085
REQUEST_NAME = 'Repository.stream_revisions_chunked'
1062
1086
path = self.bzrdir._path_for_remote_call(self._client)
1063
1087
body = self._serialise_search_recipe(search.get_recipe())
1064
response, protocol = self._client.call_with_body_bytes_expecting_body(
1065
REQUEST_NAME, (path,), body)
1067
if self._response_is_unknown_method((response, protocol), REQUEST_NAME):
1089
result = self._client.call_with_body_bytes_expecting_body(
1090
REQUEST_NAME, (path,), body)
1091
response, protocol = result
1092
except errors.UnknownSmartMethod:
1068
1093
# Server does not support this method, so fall back to VFS.
1069
1094
# Worse, we have to force a disconnection, because the server now
1070
1095
# doesn't realise it has a body on the wire to consume, so the
1160
1185
self._dir_mode = None
1161
1186
self._file_mode = None
1163
def get(self, path):
1164
"""'get' a remote path as per the LockableFiles interface.
1166
:param path: the file to 'get'. If this is 'branch.conf', we do not
1167
just retrieve a file, instead we ask the smart server to generate
1168
a configuration for us - which is retrieved as an INI file.
1170
if path == 'branch.conf':
1171
path = self.bzrdir._path_for_remote_call(self._client)
1172
response = self._client.call_expecting_body(
1173
'Branch.get_config_file', path)
1174
assert response[0][0] == 'ok', \
1175
'unexpected response code %s' % (response[0],)
1176
return StringIO(response[1].read_body_bytes())
1179
return LockableFiles.get(self, path)
1182
1189
class RemoteBranchFormat(branch.BranchFormat):
1192
1199
return 'Remote BZR Branch'
1194
1201
def open(self, a_bzrdir):
1195
assert isinstance(a_bzrdir, RemoteBzrDir)
1196
1202
return a_bzrdir.open_branch()
1198
1204
def initialize(self, a_bzrdir):
1199
assert isinstance(a_bzrdir, RemoteBzrDir)
1200
1205
return a_bzrdir.create_branch()
1202
1207
def supports_tags(self):
1248
1253
self._control_files = None
1249
1254
self._lock_mode = None
1250
1255
self._lock_token = None
1256
self._repo_lock_token = None
1251
1257
self._lock_count = 0
1252
1258
self._leave_lock = False
1260
def _ensure_real_transport(self):
1261
# if we try vfs access, return the real branch's vfs transport
1263
return self._real_branch._transport
1265
_transport = property(_ensure_real_transport)
1254
1267
def __str__(self):
1255
1268
return "%s(%s)" % (self.__class__.__name__, self.base)
1262
1275
Used before calls to self._real_branch.
1264
if not self._real_branch:
1265
assert vfs.vfs_enabled()
1277
if self._real_branch is None:
1278
if not vfs.vfs_enabled():
1279
raise AssertionError('smart server vfs must be enabled '
1280
'to use vfs implementation')
1266
1281
self.bzrdir._ensure_real()
1267
1282
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1268
1283
# Give the remote repository the matching real repo.
1313
1328
repo_token = self.repository.lock_write()
1314
1329
self.repository.unlock()
1315
1330
path = self.bzrdir._path_for_remote_call(self._client)
1316
response = self._client.call('Branch.lock_write', path, branch_token,
1318
if response[0] == 'ok':
1319
ok, branch_token, repo_token = response
1320
return branch_token, repo_token
1321
elif response[0] == 'LockContention':
1322
raise errors.LockContention('(remote lock)')
1323
elif response[0] == 'TokenMismatch':
1324
raise errors.TokenMismatch(token, '(remote token)')
1325
elif response[0] == 'UnlockableTransport':
1326
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1327
elif response[0] == 'ReadOnlyError':
1328
raise errors.ReadOnlyError(self)
1329
elif response[0] == 'LockFailed':
1330
raise errors.LockFailed(response[1], response[2])
1332
response = self._client.call(
1333
'Branch.lock_write', path, branch_token, repo_token or '')
1334
except errors.ErrorFromSmartServer, err:
1335
if err.error_verb == 'LockContention':
1336
raise errors.LockContention('(remote lock)')
1337
elif err.error_verb == 'TokenMismatch':
1338
raise errors.TokenMismatch(token, '(remote token)')
1339
elif err.error_verb == 'UnlockableTransport':
1340
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1341
elif err.error_verb == 'ReadOnlyError':
1342
raise errors.ReadOnlyError(self)
1343
elif err.error_verb == 'LockFailed':
1344
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1346
if response[0] != 'ok':
1332
1347
raise errors.UnexpectedSmartServerResponse(response)
1348
ok, branch_token, repo_token = response
1349
return branch_token, repo_token
1334
1351
def lock_write(self, token=None):
1335
1352
if not self._lock_mode:
1336
1353
remote_tokens = self._remote_lock_write(token)
1337
1354
self._lock_token, self._repo_lock_token = remote_tokens
1338
assert self._lock_token, 'Remote server did not return a token!'
1355
if not self._lock_token:
1356
raise SmartProtocolError('Remote server did not return a token!')
1339
1357
# TODO: We really, really, really don't want to call _ensure_real
1340
1358
# here, but it's the easiest way to ensure coherency between the
1341
1359
# state of the RemoteBranch and RemoteRepository objects and the
1372
1390
def _unlock(self, branch_token, repo_token):
1373
1391
path = self.bzrdir._path_for_remote_call(self._client)
1374
response = self._client.call('Branch.unlock', path, branch_token,
1393
response = self._client.call('Branch.unlock', path, branch_token,
1395
except errors.ErrorFromSmartServer, err:
1396
if err.error_verb == 'TokenMismatch':
1397
raise errors.TokenMismatch(
1398
str((branch_token, repo_token)), '(remote tokens)')
1376
1400
if response == ('ok',):
1378
elif response[0] == 'TokenMismatch':
1379
raise errors.TokenMismatch(
1380
str((branch_token, repo_token)), '(remote tokens)')
1382
raise errors.UnexpectedSmartServerResponse(response)
1402
raise errors.UnexpectedSmartServerResponse(response)
1384
1404
def unlock(self):
1385
1405
self._lock_count -= 1
1426
1447
"""See Branch.last_revision_info()."""
1427
1448
path = self.bzrdir._path_for_remote_call(self._client)
1428
1449
response = self._client.call('Branch.last_revision_info', path)
1429
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1450
if response[0] != 'ok':
1451
raise SmartProtocolError('unexpected response code %s' % (response,))
1430
1452
revno = int(response[1])
1431
1453
last_revision = response[2]
1432
1454
return (revno, last_revision)
1434
1456
def _gen_revision_history(self):
1435
1457
"""See Branch._gen_revision_history()."""
1436
1458
path = self.bzrdir._path_for_remote_call(self._client)
1437
response = self._client.call_expecting_body(
1459
response_tuple, response_handler = self._client.call_expecting_body(
1438
1460
'Branch.revision_history', path)
1439
assert response[0][0] == 'ok', ('unexpected response code %s'
1441
result = response[1].read_body_bytes().split('\x00')
1461
if response_tuple[0] != 'ok':
1462
raise UnexpectedSmartServerResponse(response_tuple)
1463
result = response_handler.read_body_bytes().split('\x00')
1442
1464
if result == ['']:
1455
1477
rev_id = rev_history[-1]
1456
1478
self._clear_cached_state()
1457
response = self._client.call('Branch.set_last_revision',
1458
path, self._lock_token, self._repo_lock_token, rev_id)
1459
if response[0] == 'NoSuchRevision':
1460
raise NoSuchRevision(self, rev_id)
1462
assert response == ('ok',), (
1463
'unexpected response code %r' % (response,))
1480
response = self._client.call('Branch.set_last_revision',
1481
path, self._lock_token, self._repo_lock_token, rev_id)
1482
except errors.ErrorFromSmartServer, err:
1483
if err.error_verb == 'NoSuchRevision':
1484
raise NoSuchRevision(self, rev_id)
1486
if response != ('ok',):
1487
raise errors.UnexpectedSmartServerResponse(response)
1464
1488
self._cache_revision_history(rev_history)
1466
1490
def get_parent(self):
1471
1495
self._ensure_real()
1472
1496
return self._real_branch.set_parent(url)
1474
def get_config(self):
1475
return RemoteBranchConfig(self)
1477
1498
def sprout(self, to_bzrdir, revision_id=None):
1478
1499
# Like Branch.sprout, except that it sprouts a branch in the default
1479
1500
# format, because RemoteBranches can't be created at arbitrary URLs.
1508
1529
def is_locked(self):
1509
1530
return self._lock_count >= 1
1511
1533
def set_last_revision_info(self, revno, revision_id):
1513
self._clear_cached_state()
1514
return self._real_branch.set_last_revision_info(revno, revision_id)
1534
revision_id = ensure_null(revision_id)
1535
path = self.bzrdir._path_for_remote_call(self._client)
1537
response = self._client.call('Branch.set_last_revision_info',
1538
path, self._lock_token, self._repo_lock_token, str(revno), revision_id)
1539
except errors.UnknownSmartMethod:
1541
self._clear_cached_state()
1542
return self._real_branch.set_last_revision_info(revno, revision_id)
1543
except errors.ErrorFromSmartServer, err:
1544
if err.error_verb == 'NoSuchRevision':
1545
raise NoSuchRevision(self, err.error_args[0])
1547
if response == ('ok',):
1548
self._clear_cached_state()
1550
raise errors.UnexpectedSmartServerResponse(response)
1516
1552
def generate_revision_history(self, revision_id, last_rev=None,
1517
1553
other_branch=None):
1534
1570
other, stop_revision=stop_revision, overwrite=overwrite)
1537
class RemoteBranchConfig(BranchConfig):
1540
self.branch._ensure_real()
1541
return self.branch._real_branch.get_config().username()
1543
def _get_branch_data_config(self):
1544
self.branch._ensure_real()
1545
if self._branch_data_config is None:
1546
self._branch_data_config = TreeConfig(self.branch._real_branch)
1547
return self._branch_data_config
1550
1573
def _extract_tar(tar, to_dir):
1551
1574
"""Extract all the contents of a tarfile object.