57
50
RemoteRepositoryFormat,
59
from bzrlib.repofmt import groupcompress_repo, knitpack_repo
52
from bzrlib.repofmt import pack_repo
60
53
from bzrlib.revision import NULL_REVISION
61
from bzrlib.smart import medium, request
54
from bzrlib.smart import server, medium
62
55
from bzrlib.smart.client import _SmartClient
63
from bzrlib.smart.repository import (
64
SmartServerRepositoryGetParentMap,
65
SmartServerRepositoryGetStream_1_19,
67
56
from bzrlib.tests import (
58
split_suite_by_condition,
70
from bzrlib.tests.scenarios import load_tests_apply_scenarios
61
from bzrlib.transport import get_transport, http
71
62
from bzrlib.transport.memory import MemoryTransport
72
63
from bzrlib.transport.remote import (
74
65
RemoteSSHTransport,
75
66
RemoteTCPTransport,
79
load_tests = load_tests_apply_scenarios
82
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
69
def load_tests(standard_tests, module, loader):
70
to_adapt, result = split_suite_by_condition(
71
standard_tests, condition_isinstance(BasicRemoteObjectTests))
72
smart_server_version_scenarios = [
86
{'transport_server': test_server.SmartTCPServer_for_testing_v2_only}),
74
{'transport_server': server.SmartTCPServer_for_testing_v2_only}),
88
{'transport_server': test_server.SmartTCPServer_for_testing})]
76
{'transport_server': server.SmartTCPServer_for_testing})]
77
return multiply_tests(to_adapt, smart_server_version_scenarios, result)
80
class BasicRemoteObjectTests(tests.TestCaseWithTransport):
92
83
super(BasicRemoteObjectTests, self).setUp()
93
84
self.transport = self.get_transport()
94
85
# make a branch that can be opened over the smart transport
95
86
self.local_wt = BzrDir.create_standalone_workingtree('.')
96
self.addCleanup(self.transport.disconnect)
89
self.transport.disconnect()
90
tests.TestCaseWithTransport.tearDown(self)
98
92
def test_create_remote_bzrdir(self):
99
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
93
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
100
94
self.assertIsInstance(b, BzrDir)
102
96
def test_open_remote_branch(self):
103
97
# open a standalone branch in the working directory
104
b = remote.RemoteBzrDir(self.transport, RemoteBzrDirFormat())
98
b = remote.RemoteBzrDir(self.transport, remote.RemoteBzrDirFormat())
105
99
branch = b.open_branch()
106
100
self.assertIsInstance(branch, Branch)
479
448
self.assertEqual(bzrdir.BzrDirMetaFormat1, type(result))
480
449
self.assertEqual(None, result._repository_format)
481
450
self.assertEqual(None, result._branch_format)
482
self.assertFinished(client)
485
class TestBzrDirOpen(TestRemote):
487
def make_fake_client_and_transport(self, path='quack'):
488
transport = MemoryTransport()
489
transport.mkdir(path)
490
transport = transport.clone(path)
491
client = FakeClient(transport.base)
492
return client, transport
494
def test_absent(self):
495
client, transport = self.make_fake_client_and_transport()
496
client.add_expected_call(
497
'BzrDir.open_2.1', ('quack/',), 'success', ('no',))
498
self.assertRaises(errors.NotBranchError, RemoteBzrDir, transport,
499
RemoteBzrDirFormat(), _client=client, _force_probe=True)
500
self.assertFinished(client)
502
def test_present_without_workingtree(self):
503
client, transport = self.make_fake_client_and_transport()
504
client.add_expected_call(
505
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'no'))
506
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
507
_client=client, _force_probe=True)
508
self.assertIsInstance(bd, RemoteBzrDir)
509
self.assertFalse(bd.has_workingtree())
510
self.assertRaises(errors.NoWorkingTree, bd.open_workingtree)
511
self.assertFinished(client)
513
def test_present_with_workingtree(self):
514
client, transport = self.make_fake_client_and_transport()
515
client.add_expected_call(
516
'BzrDir.open_2.1', ('quack/',), 'success', ('yes', 'yes'))
517
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
518
_client=client, _force_probe=True)
519
self.assertIsInstance(bd, RemoteBzrDir)
520
self.assertTrue(bd.has_workingtree())
521
self.assertRaises(errors.NotLocalUrl, bd.open_workingtree)
522
self.assertFinished(client)
524
def test_backwards_compat(self):
525
client, transport = self.make_fake_client_and_transport()
526
client.add_expected_call(
527
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
528
client.add_expected_call(
529
'BzrDir.open', ('quack/',), 'success', ('yes',))
530
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
531
_client=client, _force_probe=True)
532
self.assertIsInstance(bd, RemoteBzrDir)
533
self.assertFinished(client)
535
def test_backwards_compat_hpss_v2(self):
536
client, transport = self.make_fake_client_and_transport()
537
# Monkey-patch fake client to simulate real-world behaviour with v2
538
# server: upon first RPC call detect the protocol version, and because
539
# the version is 2 also do _remember_remote_is_before((1, 6)) before
540
# continuing with the RPC.
541
orig_check_call = client._check_call
542
def check_call(method, args):
543
client._medium._protocol_version = 2
544
client._medium._remember_remote_is_before((1, 6))
545
client._check_call = orig_check_call
546
client._check_call(method, args)
547
client._check_call = check_call
548
client.add_expected_call(
549
'BzrDir.open_2.1', ('quack/',), 'unknown', ('BzrDir.open_2.1',))
550
client.add_expected_call(
551
'BzrDir.open', ('quack/',), 'success', ('yes',))
552
bd = RemoteBzrDir(transport, RemoteBzrDirFormat(),
553
_client=client, _force_probe=True)
554
self.assertIsInstance(bd, RemoteBzrDir)
555
self.assertFinished(client)
451
client.finished_test()
558
454
class TestBzrDirOpenBranch(TestRemote):
877
740
self.assertEqual(network_name, repo._format.network_name())
880
class TestBzrDirFormatInitializeEx(TestRemote):
882
def test_success(self):
883
"""Simple test for typical successful call."""
884
fmt = RemoteBzrDirFormat()
885
default_format_name = BzrDirFormat.get_default_format().network_name()
886
transport = self.get_transport()
887
client = FakeClient(transport.base)
888
client.add_expected_call(
889
'BzrDirFormat.initialize_ex_1.16',
890
(default_format_name, 'path', 'False', 'False', 'False', '',
891
'', '', '', 'False'),
893
('.', 'no', 'no', 'yes', 'repo fmt', 'repo bzrdir fmt',
894
'bzrdir fmt', 'False', '', '', 'repo lock token'))
895
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
896
# it's currently hard to test that without supplying a real remote
897
# transport connected to a real server.
898
result = fmt._initialize_on_transport_ex_rpc(client, 'path',
899
transport, False, False, False, None, None, None, None, False)
900
self.assertFinished(client)
902
def test_error(self):
903
"""Error responses are translated, e.g. 'PermissionDenied' raises the
904
corresponding error from the client.
906
fmt = RemoteBzrDirFormat()
907
default_format_name = BzrDirFormat.get_default_format().network_name()
908
transport = self.get_transport()
909
client = FakeClient(transport.base)
910
client.add_expected_call(
911
'BzrDirFormat.initialize_ex_1.16',
912
(default_format_name, 'path', 'False', 'False', 'False', '',
913
'', '', '', 'False'),
915
('PermissionDenied', 'path', 'extra info'))
916
# XXX: It would be better to call fmt.initialize_on_transport_ex, but
917
# it's currently hard to test that without supplying a real remote
918
# transport connected to a real server.
919
err = self.assertRaises(errors.PermissionDenied,
920
fmt._initialize_on_transport_ex_rpc, client, 'path', transport,
921
False, False, False, None, None, None, None, False)
922
self.assertEqual('path', err.path)
923
self.assertEqual(': extra info', err.extra)
924
self.assertFinished(client)
926
def test_error_from_real_server(self):
927
"""Integration test for error translation."""
928
transport = self.make_smart_server('foo')
929
transport = transport.clone('no-such-path')
930
fmt = RemoteBzrDirFormat()
931
err = self.assertRaises(errors.NoSuchFile,
932
fmt.initialize_on_transport_ex, transport, create_prefix=False)
935
743
class OldSmartClient(object):
936
744
"""A fake smart client for test_old_version that just returns a version one
937
745
response to the 'hello' (query version) command.
1120
863
transport = transport.clone('quack')
1121
864
branch = self.make_remote_branch(transport, client)
1122
865
result = branch.tags.get_tag_dict()
1123
self.assertFinished(client)
866
client.finished_test()
1124
867
self.assertEqual({}, result)
1127
class TestBranchSetTagsBytes(RemoteBranchTestCase):
1129
def test_trivial(self):
1130
transport = MemoryTransport()
1131
client = FakeClient(transport.base)
1132
client.add_expected_call(
1133
'Branch.get_stacked_on_url', ('quack/',),
1134
'error', ('NotStacked',))
1135
client.add_expected_call(
1136
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1138
transport.mkdir('quack')
1139
transport = transport.clone('quack')
1140
branch = self.make_remote_branch(transport, client)
1141
self.lock_remote_branch(branch)
1142
branch._set_tags_bytes('tags bytes')
1143
self.assertFinished(client)
1144
self.assertEqual('tags bytes', client._calls[-1][-1])
1146
def test_backwards_compatible(self):
1147
transport = MemoryTransport()
1148
client = FakeClient(transport.base)
1149
client.add_expected_call(
1150
'Branch.get_stacked_on_url', ('quack/',),
1151
'error', ('NotStacked',))
1152
client.add_expected_call(
1153
'Branch.set_tags_bytes', ('quack/', 'branch token', 'repo token'),
1154
'unknown', ('Branch.set_tags_bytes',))
1155
transport.mkdir('quack')
1156
transport = transport.clone('quack')
1157
branch = self.make_remote_branch(transport, client)
1158
self.lock_remote_branch(branch)
1159
class StubRealBranch(object):
1162
def _set_tags_bytes(self, bytes):
1163
self.calls.append(('set_tags_bytes', bytes))
1164
real_branch = StubRealBranch()
1165
branch._real_branch = real_branch
1166
branch._set_tags_bytes('tags bytes')
1167
# Call a second time, to exercise the 'remote version already inferred'
1169
branch._set_tags_bytes('tags bytes')
1170
self.assertFinished(client)
1172
[('set_tags_bytes', 'tags bytes')] * 2, real_branch.calls)
1175
class TestBranchHeadsToFetch(RemoteBranchTestCase):
1177
def test_uses_last_revision_info_and_tags_by_default(self):
1178
transport = MemoryTransport()
1179
client = FakeClient(transport.base)
1180
client.add_expected_call(
1181
'Branch.get_stacked_on_url', ('quack/',),
1182
'error', ('NotStacked',))
1183
client.add_expected_call(
1184
'Branch.last_revision_info', ('quack/',),
1185
'success', ('ok', '1', 'rev-tip'))
1186
client.add_expected_call(
1187
'Branch.get_config_file', ('quack/',),
1188
'success', ('ok',), '')
1189
transport.mkdir('quack')
1190
transport = transport.clone('quack')
1191
branch = self.make_remote_branch(transport, client)
1192
result = branch.heads_to_fetch()
1193
self.assertFinished(client)
1194
self.assertEqual((set(['rev-tip']), set()), result)
1196
def test_uses_last_revision_info_and_tags_when_set(self):
1197
transport = MemoryTransport()
1198
client = FakeClient(transport.base)
1199
client.add_expected_call(
1200
'Branch.get_stacked_on_url', ('quack/',),
1201
'error', ('NotStacked',))
1202
client.add_expected_call(
1203
'Branch.last_revision_info', ('quack/',),
1204
'success', ('ok', '1', 'rev-tip'))
1205
client.add_expected_call(
1206
'Branch.get_config_file', ('quack/',),
1207
'success', ('ok',), 'branch.fetch_tags = True')
1208
# XXX: this will break if the default format's serialization of tags
1209
# changes, or if the RPC for fetching tags changes from get_tags_bytes.
1210
client.add_expected_call(
1211
'Branch.get_tags_bytes', ('quack/',),
1212
'success', ('d5:tag-17:rev-foo5:tag-27:rev-bare',))
1213
transport.mkdir('quack')
1214
transport = transport.clone('quack')
1215
branch = self.make_remote_branch(transport, client)
1216
result = branch.heads_to_fetch()
1217
self.assertFinished(client)
1219
(set(['rev-tip']), set(['rev-foo', 'rev-bar'])), result)
1221
def test_uses_rpc_for_formats_with_non_default_heads_to_fetch(self):
1222
transport = MemoryTransport()
1223
client = FakeClient(transport.base)
1224
client.add_expected_call(
1225
'Branch.get_stacked_on_url', ('quack/',),
1226
'error', ('NotStacked',))
1227
client.add_expected_call(
1228
'Branch.heads_to_fetch', ('quack/',),
1229
'success', (['tip'], ['tagged-1', 'tagged-2']))
1230
transport.mkdir('quack')
1231
transport = transport.clone('quack')
1232
branch = self.make_remote_branch(transport, client)
1233
branch._format._use_default_local_heads_to_fetch = lambda: False
1234
result = branch.heads_to_fetch()
1235
self.assertFinished(client)
1236
self.assertEqual((set(['tip']), set(['tagged-1', 'tagged-2'])), result)
1238
def make_branch_with_tags(self):
1239
self.setup_smart_server_with_call_log()
1240
# Make a branch with a single revision.
1241
builder = self.make_branch_builder('foo')
1242
builder.start_series()
1243
builder.build_snapshot('tip', None, [
1244
('add', ('', 'root-id', 'directory', ''))])
1245
builder.finish_series()
1246
branch = builder.get_branch()
1247
# Add two tags to that branch
1248
branch.tags.set_tag('tag-1', 'rev-1')
1249
branch.tags.set_tag('tag-2', 'rev-2')
1252
def test_backwards_compatible(self):
1253
branch = self.make_branch_with_tags()
1254
c = branch.get_config()
1255
c.set_user_option('branch.fetch_tags', 'True')
1256
self.addCleanup(branch.lock_read().unlock)
1257
# Disable the heads_to_fetch verb
1258
verb = 'Branch.heads_to_fetch'
1259
self.disable_verb(verb)
1260
self.reset_smart_call_log()
1261
result = branch.heads_to_fetch()
1262
self.assertEqual((set(['tip']), set(['rev-1', 'rev-2'])), result)
1264
['Branch.last_revision_info', 'Branch.get_config_file',
1265
'Branch.get_tags_bytes'],
1266
[call.call.method for call in self.hpss_calls])
1268
def test_backwards_compatible_no_tags(self):
1269
branch = self.make_branch_with_tags()
1270
c = branch.get_config()
1271
c.set_user_option('branch.fetch_tags', 'False')
1272
self.addCleanup(branch.lock_read().unlock)
1273
# Disable the heads_to_fetch verb
1274
verb = 'Branch.heads_to_fetch'
1275
self.disable_verb(verb)
1276
self.reset_smart_call_log()
1277
result = branch.heads_to_fetch()
1278
self.assertEqual((set(['tip']), set()), result)
1280
['Branch.last_revision_info', 'Branch.get_config_file'],
1281
[call.call.method for call in self.hpss_calls])
1284
870
class TestBranchLastRevisionInfo(RemoteBranchTestCase):
1286
872
def test_empty_branch(self):
1736
1332
self.assertEqual('rejection message', err.msg)
1739
class TestBranchGetSetConfig(RemoteBranchTestCase):
1335
class TestBranchControlGetBranchConf(tests.TestCaseWithMemoryTransport):
1336
"""Getting the branch configuration should use an abstract method not vfs.
1741
1339
def test_get_branch_conf(self):
1742
# in an empty branch we decode the response properly
1743
client = FakeClient()
1744
client.add_expected_call(
1745
'Branch.get_stacked_on_url', ('memory:///',),
1746
'error', ('NotStacked',),)
1747
client.add_success_response_with_body('# config file body', 'ok')
1748
transport = MemoryTransport()
1749
branch = self.make_remote_branch(transport, client)
1750
config = branch.get_config()
1751
config.has_explicit_nickname()
1753
[('call', 'Branch.get_stacked_on_url', ('memory:///',)),
1754
('call_expecting_body', 'Branch.get_config_file', ('memory:///',))],
1757
def test_get_multi_line_branch_conf(self):
1758
# Make sure that multiple-line branch.conf files are supported
1760
# https://bugs.launchpad.net/bzr/+bug/354075
1761
client = FakeClient()
1762
client.add_expected_call(
1763
'Branch.get_stacked_on_url', ('memory:///',),
1764
'error', ('NotStacked',),)
1765
client.add_success_response_with_body('a = 1\nb = 2\nc = 3\n', 'ok')
1766
transport = MemoryTransport()
1767
branch = self.make_remote_branch(transport, client)
1768
config = branch.get_config()
1769
self.assertEqual(u'2', config.get_user_option('b'))
1771
def test_set_option(self):
1772
client = FakeClient()
1773
client.add_expected_call(
1774
'Branch.get_stacked_on_url', ('memory:///',),
1775
'error', ('NotStacked',),)
1776
client.add_expected_call(
1777
'Branch.lock_write', ('memory:///', '', ''),
1778
'success', ('ok', 'branch token', 'repo token'))
1779
client.add_expected_call(
1780
'Branch.set_config_option', ('memory:///', 'branch token',
1781
'repo token', 'foo', 'bar', ''),
1783
client.add_expected_call(
1784
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1786
transport = MemoryTransport()
1787
branch = self.make_remote_branch(transport, client)
1789
config = branch._get_config()
1790
config.set_option('foo', 'bar')
1792
self.assertFinished(client)
1794
def test_set_option_with_dict(self):
1795
client = FakeClient()
1796
client.add_expected_call(
1797
'Branch.get_stacked_on_url', ('memory:///',),
1798
'error', ('NotStacked',),)
1799
client.add_expected_call(
1800
'Branch.lock_write', ('memory:///', '', ''),
1801
'success', ('ok', 'branch token', 'repo token'))
1802
encoded_dict_value = 'd5:ascii1:a11:unicode \xe2\x8c\x9a3:\xe2\x80\xbde'
1803
client.add_expected_call(
1804
'Branch.set_config_option_dict', ('memory:///', 'branch token',
1805
'repo token', encoded_dict_value, 'foo', ''),
1807
client.add_expected_call(
1808
'Branch.unlock', ('memory:///', 'branch token', 'repo token'),
1810
transport = MemoryTransport()
1811
branch = self.make_remote_branch(transport, client)
1813
config = branch._get_config()
1815
{'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'},
1818
self.assertFinished(client)
1820
def test_backwards_compat_set_option(self):
1821
self.setup_smart_server_with_call_log()
1822
branch = self.make_branch('.')
1823
verb = 'Branch.set_config_option'
1824
self.disable_verb(verb)
1826
self.addCleanup(branch.unlock)
1827
self.reset_smart_call_log()
1828
branch._get_config().set_option('value', 'name')
1829
self.assertLength(10, self.hpss_calls)
1830
self.assertEqual('value', branch._get_config().get_option('name'))
1832
def test_backwards_compat_set_option_with_dict(self):
1833
self.setup_smart_server_with_call_log()
1834
branch = self.make_branch('.')
1835
verb = 'Branch.set_config_option_dict'
1836
self.disable_verb(verb)
1838
self.addCleanup(branch.unlock)
1839
self.reset_smart_call_log()
1840
config = branch._get_config()
1841
value_dict = {'ascii': 'a', u'unicode \N{WATCH}': u'\N{INTERROBANG}'}
1842
config.set_option(value_dict, 'name')
1843
self.assertLength(10, self.hpss_calls)
1844
self.assertEqual(value_dict, branch._get_config().get_option('name'))
1340
raise tests.KnownFailure('branch.conf is not retrieved by get_config_file')
1341
## # We should see that branch.get_config() does a single rpc to get the
1342
## # remote configuration file, abstracting away where that is stored on
1343
## # the server. However at the moment it always falls back to using the
1344
## # vfs, and this would need some changes in config.py.
1346
## # in an empty branch we decode the response properly
1347
## client = FakeClient([(('ok', ), '# config file body')], self.get_url())
1348
## # we need to make a real branch because the remote_branch.control_files
1349
## # will trigger _ensure_real.
1350
## branch = self.make_branch('quack')
1351
## transport = branch.bzrdir.root_transport
1352
## # we do not want bzrdir to make any remote calls
1353
## bzrdir = RemoteBzrDir(transport, _client=False)
1354
## branch = RemoteBranch(bzrdir, None, _client=client)
1355
## config = branch.get_config()
1356
## self.assertEqual(
1357
## [('call_expecting_body', 'Branch.get_config_file', ('quack/',))],
1847
1361
class TestBranchLockWrite(RemoteBranchTestCase):
2369
1795
self.assertEqual(('AnUnexpectedError',), e.error_tuple)
2372
class TestRepositoryGetRevIdForRevno(TestRemoteRepository):
2375
repo, client = self.setup_fake_client_and_repository('quack')
2376
client.add_expected_call(
2377
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2378
'success', ('ok', 'rev-five'))
2379
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2380
self.assertEqual((True, 'rev-five'), result)
2381
self.assertFinished(client)
2383
def test_history_incomplete(self):
2384
repo, client = self.setup_fake_client_and_repository('quack')
2385
client.add_expected_call(
2386
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2387
'success', ('history-incomplete', 10, 'rev-ten'))
2388
result = repo.get_rev_id_for_revno(5, (42, 'rev-foo'))
2389
self.assertEqual((False, (10, 'rev-ten')), result)
2390
self.assertFinished(client)
2392
def test_history_incomplete_with_fallback(self):
2393
"""A 'history-incomplete' response causes the fallback repository to be
2394
queried too, if one is set.
2396
# Make a repo with a fallback repo, both using a FakeClient.
2397
format = remote.response_tuple_to_repo_format(
2398
('yes', 'no', 'yes', self.get_repo_format().network_name()))
2399
repo, client = self.setup_fake_client_and_repository('quack')
2400
repo._format = format
2401
fallback_repo, ignored = self.setup_fake_client_and_repository(
2403
fallback_repo._client = client
2404
fallback_repo._format = format
2405
repo.add_fallback_repository(fallback_repo)
2406
# First the client should ask the primary repo
2407
client.add_expected_call(
2408
'Repository.get_rev_id_for_revno', ('quack/', 1, (42, 'rev-foo')),
2409
'success', ('history-incomplete', 2, 'rev-two'))
2410
# Then it should ask the fallback, using revno/revid from the
2411
# history-incomplete response as the known revno/revid.
2412
client.add_expected_call(
2413
'Repository.get_rev_id_for_revno',('fallback/', 1, (2, 'rev-two')),
2414
'success', ('ok', 'rev-one'))
2415
result = repo.get_rev_id_for_revno(1, (42, 'rev-foo'))
2416
self.assertEqual((True, 'rev-one'), result)
2417
self.assertFinished(client)
2419
def test_nosuchrevision(self):
2420
# 'nosuchrevision' is returned when the known-revid is not found in the
2421
# remote repo. The client translates that response to NoSuchRevision.
2422
repo, client = self.setup_fake_client_and_repository('quack')
2423
client.add_expected_call(
2424
'Repository.get_rev_id_for_revno', ('quack/', 5, (42, 'rev-foo')),
2425
'error', ('nosuchrevision', 'rev-foo'))
2427
errors.NoSuchRevision,
2428
repo.get_rev_id_for_revno, 5, (42, 'rev-foo'))
2429
self.assertFinished(client)
2431
def test_branch_fallback_locking(self):
2432
"""RemoteBranch.get_rev_id takes a read lock, and tries to call the
2433
get_rev_id_for_revno verb. If the verb is unknown the VFS fallback
2434
will be invoked, which will fail if the repo is unlocked.
2436
self.setup_smart_server_with_call_log()
2437
tree = self.make_branch_and_memory_tree('.')
2440
rev1 = tree.commit('First')
2441
rev2 = tree.commit('Second')
2443
branch = tree.branch
2444
self.assertFalse(branch.is_locked())
2445
self.reset_smart_call_log()
2446
verb = 'Repository.get_rev_id_for_revno'
2447
self.disable_verb(verb)
2448
self.assertEqual(rev1, branch.get_rev_id(1))
2449
self.assertLength(1, [call for call in self.hpss_calls if
2450
call.call.method == verb])
2453
1798
class TestRepositoryIsShared(TestRemoteRepository):
2455
1800
def test_is_shared(self):
2570
1915
self.assertEqual([], client._calls)
2573
class TestRepositoryInsertStreamBase(TestRemoteRepository):
2574
"""Base class for Repository.insert_stream and .insert_stream_1.19
2578
def checkInsertEmptyStream(self, repo, client):
2579
"""Insert an empty stream, checking the result.
2581
This checks that there are no resume_tokens or missing_keys, and that
2582
the client is finished.
2584
sink = repo._get_sink()
2585
fmt = repository.format_registry.get_default()
2586
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
2587
self.assertEqual([], resume_tokens)
2588
self.assertEqual(set(), missing_keys)
2589
self.assertFinished(client)
2592
class TestRepositoryInsertStream(TestRepositoryInsertStreamBase):
2593
"""Tests for using Repository.insert_stream verb when the _1.19 variant is
2596
This test case is very similar to TestRepositoryInsertStream_1_19.
2600
TestRemoteRepository.setUp(self)
2601
self.disable_verb('Repository.insert_stream_1.19')
2603
def test_unlocked_repo(self):
2604
transport_path = 'quack'
2605
repo, client = self.setup_fake_client_and_repository(transport_path)
2606
client.add_expected_call(
2607
'Repository.insert_stream_1.19', ('quack/', ''),
2608
'unknown', ('Repository.insert_stream_1.19',))
2609
client.add_expected_call(
2610
'Repository.insert_stream', ('quack/', ''),
2612
client.add_expected_call(
2613
'Repository.insert_stream', ('quack/', ''),
2615
self.checkInsertEmptyStream(repo, client)
2617
def test_locked_repo_with_no_lock_token(self):
2618
transport_path = 'quack'
2619
repo, client = self.setup_fake_client_and_repository(transport_path)
2620
client.add_expected_call(
2621
'Repository.lock_write', ('quack/', ''),
2622
'success', ('ok', ''))
2623
client.add_expected_call(
2624
'Repository.insert_stream_1.19', ('quack/', ''),
2625
'unknown', ('Repository.insert_stream_1.19',))
2626
client.add_expected_call(
2627
'Repository.insert_stream', ('quack/', ''),
2629
client.add_expected_call(
2630
'Repository.insert_stream', ('quack/', ''),
2633
self.checkInsertEmptyStream(repo, client)
2635
def test_locked_repo_with_lock_token(self):
2636
transport_path = 'quack'
2637
repo, client = self.setup_fake_client_and_repository(transport_path)
2638
client.add_expected_call(
2639
'Repository.lock_write', ('quack/', ''),
2640
'success', ('ok', 'a token'))
2641
client.add_expected_call(
2642
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2643
'unknown', ('Repository.insert_stream_1.19',))
2644
client.add_expected_call(
2645
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2647
client.add_expected_call(
2648
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
2651
self.checkInsertEmptyStream(repo, client)
2653
def test_stream_with_inventory_deltas(self):
2654
"""'inventory-deltas' substreams cannot be sent to the
2655
Repository.insert_stream verb, because not all servers that implement
2656
that verb will accept them. So when one is encountered the RemoteSink
2657
immediately stops using that verb and falls back to VFS insert_stream.
2659
transport_path = 'quack'
2660
repo, client = self.setup_fake_client_and_repository(transport_path)
2661
client.add_expected_call(
2662
'Repository.insert_stream_1.19', ('quack/', ''),
2663
'unknown', ('Repository.insert_stream_1.19',))
2664
client.add_expected_call(
2665
'Repository.insert_stream', ('quack/', ''),
2667
client.add_expected_call(
2668
'Repository.insert_stream', ('quack/', ''),
2670
# Create a fake real repository for insert_stream to fall back on, so
2671
# that we can directly see the records the RemoteSink passes to the
2676
def insert_stream(self, stream, src_format, resume_tokens):
2677
for substream_kind, substream in stream:
2678
self.records.append(
2679
(substream_kind, [record.key for record in substream]))
2680
return ['fake tokens'], ['fake missing keys']
2681
fake_real_sink = FakeRealSink()
2682
class FakeRealRepository:
2683
def _get_sink(self):
2684
return fake_real_sink
2685
def is_in_write_group(self):
2687
def refresh_data(self):
2689
repo._real_repository = FakeRealRepository()
2690
sink = repo._get_sink()
2691
fmt = repository.format_registry.get_default()
2692
stream = self.make_stream_with_inv_deltas(fmt)
2693
resume_tokens, missing_keys = sink.insert_stream(stream, fmt, [])
2694
# Every record from the first inventory delta should have been sent to
2696
expected_records = [
2697
('inventory-deltas', [('rev2',), ('rev3',)]),
2698
('texts', [('some-rev', 'some-file')])]
2699
self.assertEqual(expected_records, fake_real_sink.records)
2700
# The return values from the real sink's insert_stream are propagated
2701
# back to the original caller.
2702
self.assertEqual(['fake tokens'], resume_tokens)
2703
self.assertEqual(['fake missing keys'], missing_keys)
2704
self.assertFinished(client)
2706
def make_stream_with_inv_deltas(self, fmt):
2707
"""Make a simple stream with an inventory delta followed by more
2708
records and more substreams to test that all records and substreams
2709
from that point on are used.
2711
This sends, in order:
2712
* inventories substream: rev1, rev2, rev3. rev2 and rev3 are
2714
* texts substream: (some-rev, some-file)
2716
# Define a stream using generators so that it isn't rewindable.
2717
inv = inventory.Inventory(revision_id='rev1')
2718
inv.root.revision = 'rev1'
2719
def stream_with_inv_delta():
2720
yield ('inventories', inventories_substream())
2721
yield ('inventory-deltas', inventory_delta_substream())
2723
versionedfile.FulltextContentFactory(
2724
('some-rev', 'some-file'), (), None, 'content')])
2725
def inventories_substream():
2726
# An empty inventory fulltext. This will be streamed normally.
2727
text = fmt._serializer.write_inventory_to_string(inv)
2728
yield versionedfile.FulltextContentFactory(
2729
('rev1',), (), None, text)
2730
def inventory_delta_substream():
2731
# An inventory delta. This can't be streamed via this verb, so it
2732
# will trigger a fallback to VFS insert_stream.
2733
entry = inv.make_entry(
2734
'directory', 'newdir', inv.root.file_id, 'newdir-id')
2735
entry.revision = 'ghost'
2736
delta = [(None, 'newdir', 'newdir-id', entry)]
2737
serializer = inventory_delta.InventoryDeltaSerializer(
2738
versioned_root=True, tree_references=False)
2739
lines = serializer.delta_to_lines('rev1', 'rev2', delta)
2740
yield versionedfile.ChunkedContentFactory(
2741
('rev2',), (('rev1',)), None, lines)
2743
lines = serializer.delta_to_lines('rev1', 'rev3', delta)
2744
yield versionedfile.ChunkedContentFactory(
2745
('rev3',), (('rev1',)), None, lines)
2746
return stream_with_inv_delta()
2749
class TestRepositoryInsertStream_1_19(TestRepositoryInsertStreamBase):
2751
def test_unlocked_repo(self):
2752
transport_path = 'quack'
2753
repo, client = self.setup_fake_client_and_repository(transport_path)
2754
client.add_expected_call(
2755
'Repository.insert_stream_1.19', ('quack/', ''),
2757
client.add_expected_call(
2758
'Repository.insert_stream_1.19', ('quack/', ''),
2760
self.checkInsertEmptyStream(repo, client)
2762
def test_locked_repo_with_no_lock_token(self):
2763
transport_path = 'quack'
2764
repo, client = self.setup_fake_client_and_repository(transport_path)
2765
client.add_expected_call(
2766
'Repository.lock_write', ('quack/', ''),
2767
'success', ('ok', ''))
2768
client.add_expected_call(
2769
'Repository.insert_stream_1.19', ('quack/', ''),
2771
client.add_expected_call(
2772
'Repository.insert_stream_1.19', ('quack/', ''),
2775
self.checkInsertEmptyStream(repo, client)
2777
def test_locked_repo_with_lock_token(self):
2778
transport_path = 'quack'
2779
repo, client = self.setup_fake_client_and_repository(transport_path)
2780
client.add_expected_call(
2781
'Repository.lock_write', ('quack/', ''),
2782
'success', ('ok', 'a token'))
2783
client.add_expected_call(
2784
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2786
client.add_expected_call(
2787
'Repository.insert_stream_1.19', ('quack/', '', 'a token'),
2790
self.checkInsertEmptyStream(repo, client)
1918
class TestRepositoryInsertStream(TestRemoteRepository):
1920
def test_unlocked_repo(self):
1921
transport_path = 'quack'
1922
repo, client = self.setup_fake_client_and_repository(transport_path)
1923
client.add_expected_call(
1924
'Repository.insert_stream', ('quack/', ''),
1926
client.add_expected_call(
1927
'Repository.insert_stream', ('quack/', ''),
1929
sink = repo._get_sink()
1930
fmt = repository.RepositoryFormat.get_default_format()
1931
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1932
self.assertEqual([], resume_tokens)
1933
self.assertEqual(set(), missing_keys)
1934
client.finished_test()
1936
def test_locked_repo_with_no_lock_token(self):
1937
transport_path = 'quack'
1938
repo, client = self.setup_fake_client_and_repository(transport_path)
1939
client.add_expected_call(
1940
'Repository.lock_write', ('quack/', ''),
1941
'success', ('ok', ''))
1942
client.add_expected_call(
1943
'Repository.insert_stream', ('quack/', ''),
1945
client.add_expected_call(
1946
'Repository.insert_stream', ('quack/', ''),
1949
sink = repo._get_sink()
1950
fmt = repository.RepositoryFormat.get_default_format()
1951
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1952
self.assertEqual([], resume_tokens)
1953
self.assertEqual(set(), missing_keys)
1954
client.finished_test()
1956
def test_locked_repo_with_lock_token(self):
1957
transport_path = 'quack'
1958
repo, client = self.setup_fake_client_and_repository(transport_path)
1959
client.add_expected_call(
1960
'Repository.lock_write', ('quack/', ''),
1961
'success', ('ok', 'a token'))
1962
client.add_expected_call(
1963
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
1965
client.add_expected_call(
1966
'Repository.insert_stream_locked', ('quack/', '', 'a token'),
1969
sink = repo._get_sink()
1970
fmt = repository.RepositoryFormat.get_default_format()
1971
resume_tokens, missing_keys = sink.insert_stream([], fmt, [])
1972
self.assertEqual([], resume_tokens)
1973
self.assertEqual(set(), missing_keys)
1974
client.finished_test()
2793
1977
class TestRepositoryTarball(TestRemoteRepository):
3388
2453
def test_copy_content_into_avoids_revision_history(self):
3389
2454
local = self.make_branch('local')
3390
builder = self.make_branch_builder('remote')
3391
builder.build_commit(message="Commit.")
2455
remote_backing_tree = self.make_branch_and_tree('remote')
2456
remote_backing_tree.commit("Commit.")
3392
2457
remote_branch_url = self.smart_server.get_url() + 'remote'
3393
2458
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3394
2459
local.repository.fetch(remote_branch.repository)
3395
2460
self.hpss_calls = []
3396
2461
remote_branch.copy_content_into(local)
3397
2462
self.assertFalse('Branch.revision_history' in self.hpss_calls)
3399
def test_fetch_everything_needs_just_one_call(self):
3400
local = self.make_branch('local')
3401
builder = self.make_branch_builder('remote')
3402
builder.build_commit(message="Commit.")
3403
remote_branch_url = self.smart_server.get_url() + 'remote'
3404
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3405
self.hpss_calls = []
3406
local.repository.fetch(
3407
remote_branch.repository,
3408
fetch_spec=_mod_graph.EverythingResult(remote_branch.repository))
3409
self.assertEqual(['Repository.get_stream_1.19'], self.hpss_calls)
3411
def override_verb(self, verb_name, verb):
3412
request_handlers = request.request_handlers
3413
orig_verb = request_handlers.get(verb_name)
3414
request_handlers.register(verb_name, verb, override_existing=True)
3415
self.addCleanup(request_handlers.register, verb_name, orig_verb,
3416
override_existing=True)
3418
def test_fetch_everything_backwards_compat(self):
3419
"""Can fetch with EverythingResult even with pre 2.4 servers.
3421
Pre-2.4 do not support 'everything' searches with the
3422
Repository.get_stream_1.19 verb.
3425
class OldGetStreamVerb(SmartServerRepositoryGetStream_1_19):
3426
"""A version of the Repository.get_stream_1.19 verb patched to
3427
reject 'everything' searches the way 2.3 and earlier do.
3429
def recreate_search(self, repository, search_bytes,
3430
discard_excess=False):
3431
verb_log.append(search_bytes.split('\n', 1)[0])
3432
if search_bytes == 'everything':
3434
request.FailedSmartServerResponse(('BadSearch',)))
3435
return super(OldGetStreamVerb,
3436
self).recreate_search(repository, search_bytes,
3437
discard_excess=discard_excess)
3438
self.override_verb('Repository.get_stream_1.19', OldGetStreamVerb)
3439
local = self.make_branch('local')
3440
builder = self.make_branch_builder('remote')
3441
builder.build_commit(message="Commit.")
3442
remote_branch_url = self.smart_server.get_url() + 'remote'
3443
remote_branch = bzrdir.BzrDir.open(remote_branch_url).open_branch()
3444
self.hpss_calls = []
3445
local.repository.fetch(
3446
remote_branch.repository,
3447
fetch_spec=_mod_graph.EverythingResult(remote_branch.repository))
3448
# make sure the overridden verb was used
3449
self.assertLength(1, verb_log)
3450
# more than one HPSS call is needed, but because it's a VFS callback
3451
# its hard to predict exactly how many.
3452
self.assertTrue(len(self.hpss_calls) > 1)
3455
class TestUpdateBoundBranchWithModifiedBoundLocation(
3456
tests.TestCaseWithTransport):
3457
"""Ensure correct handling of bound_location modifications.
3459
This is tested against a smart server as http://pad.lv/786980 was about a
3460
ReadOnlyError (write attempt during a read-only transaction) which can only
3461
happen in this context.
3465
super(TestUpdateBoundBranchWithModifiedBoundLocation, self).setUp()
3466
self.transport_server = test_server.SmartTCPServer_for_testing
3468
def make_master_and_checkout(self, master_name, checkout_name):
3469
# Create the master branch and its associated checkout
3470
self.master = self.make_branch_and_tree(master_name)
3471
self.checkout = self.master.branch.create_checkout(checkout_name)
3472
# Modify the master branch so there is something to update
3473
self.master.commit('add stuff')
3474
self.last_revid = self.master.commit('even more stuff')
3475
self.bound_location = self.checkout.branch.get_bound_location()
3477
def assertUpdateSucceeds(self, new_location):
3478
self.checkout.branch.set_bound_location(new_location)
3479
self.checkout.update()
3480
self.assertEquals(self.last_revid, self.checkout.last_revision())
3482
def test_without_final_slash(self):
3483
self.make_master_and_checkout('master', 'checkout')
3484
# For unclear reasons some users have a bound_location without a final
3485
# '/', simulate that by forcing such a value
3486
self.assertEndsWith(self.bound_location, '/')
3487
self.assertUpdateSucceeds(self.bound_location.rstrip('/'))
3489
def test_plus_sign(self):
3490
self.make_master_and_checkout('+master', 'checkout')
3491
self.assertUpdateSucceeds(self.bound_location.replace('%2B', '+', 1))
3493
def test_tilda(self):
3494
# Embed ~ in the middle of the path just to avoid any $HOME
3496
self.make_master_and_checkout('mas~ter', 'checkout')
3497
self.assertUpdateSucceeds(self.bound_location.replace('%2E', '~', 1))