913
927
tree.add([''], ['TREE_ROOT'])
914
928
tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
915
929
self.b1 = tree.branch
916
bundle_sio, revision_ids = self.create_bundle_text(None, 'rev1')
930
bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
931
bundle = read_bundle(bundle_sio)
932
revision_info = bundle.revisions[0]
933
self.assertEqual('rev1', revision_info.revision_id)
934
rev = revision_info.as_revision()
935
self.assertEqual({'branch-nick':'tree', 'empty':'', 'one':'two'},
938
def test_bundle_sorted_properties(self):
939
"""For stability the writer should write properties in sorted order."""
940
tree = self.make_branch_and_memory_tree('tree')
942
self.addCleanup(tree.unlock)
944
tree.add([''], ['TREE_ROOT'])
945
tree.commit('One', rev_id='rev1',
946
revprops={'a':'4', 'b':'3', 'c':'2', 'd':'1'})
947
self.b1 = tree.branch
948
bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
949
bundle = read_bundle(bundle_sio)
950
revision_info = bundle.revisions[0]
951
self.assertEqual('rev1', revision_info.revision_id)
952
rev = revision_info.as_revision()
953
self.assertEqual({'branch-nick':'tree', 'a':'4', 'b':'3', 'c':'2',
954
'd':'1'}, rev.properties)
956
def test_bundle_unicode_properties(self):
957
"""We should be able to round trip a non-ascii property."""
958
tree = self.make_branch_and_memory_tree('tree')
960
self.addCleanup(tree.unlock)
962
tree.add([''], ['TREE_ROOT'])
963
# Revisions themselves do not require anything about revision property
964
# keys, other than that they are a basestring, and do not contain
966
# However, Testaments assert than they are str(), and thus should not
968
tree.commit('One', rev_id='rev1',
969
revprops={'omega':u'\u03a9', 'alpha':u'\u03b1'})
970
self.b1 = tree.branch
971
bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
972
bundle = read_bundle(bundle_sio)
973
revision_info = bundle.revisions[0]
974
self.assertEqual('rev1', revision_info.revision_id)
975
rev = revision_info.as_revision()
976
self.assertEqual({'branch-nick':'tree', 'omega':u'\u03a9',
977
'alpha':u'\u03b1'}, rev.properties)
979
def test_bundle_with_ghosts(self):
980
tree = self.make_branch_and_tree('tree')
981
self.b1 = tree.branch
982
self.build_tree_contents([('tree/file', 'content1')])
985
self.build_tree_contents([('tree/file', 'content2')])
986
tree.add_parent_tree_id('ghost')
987
tree.commit('rev2', rev_id='rev2')
988
bundle = self.get_valid_bundle('null:', 'rev2')
990
def make_simple_tree(self, format=None):
991
tree = self.make_branch_and_tree('b1', format=format)
992
self.b1 = tree.branch
993
self.build_tree(['b1/file'])
997
def test_across_serializers(self):
998
tree = self.make_simple_tree('knit')
999
tree.commit('hello', rev_id='rev1')
1000
tree.commit('hello', rev_id='rev2')
1001
bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1002
repo = self.make_repository('repo', format='dirstate-with-subtree')
1003
bundle.install_revisions(repo)
1004
inv_text = repo.get_inventory_xml('rev2')
1005
self.assertNotContainsRe(inv_text, 'format="5"')
1006
self.assertContainsRe(inv_text, 'format="7"')
1008
def test_across_models(self):
1009
tree = self.make_simple_tree('knit')
1010
tree.commit('hello', rev_id='rev1')
1011
tree.commit('hello', rev_id='rev2')
1012
bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1013
repo = self.make_repository('repo', format='dirstate-with-subtree')
1014
bundle.install_revisions(repo)
1015
inv = repo.get_inventory('rev2')
1016
self.assertEqual('rev2', inv.root.revision)
1017
root_vf = repo.weave_store.get_weave(inv.root.file_id,
1018
repo.get_transaction())
1019
self.assertEqual(root_vf.versions(), ['rev1', 'rev2'])
1021
def test_across_models_incompatible(self):
1022
tree = self.make_simple_tree('dirstate-with-subtree')
1023
tree.commit('hello', rev_id='rev1')
1024
tree.commit('hello', rev_id='rev2')
1026
bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1027
except errors.IncompatibleBundleFormat:
1028
raise TestSkipped("Format 0.8 doesn't work with knit3")
1029
repo = self.make_repository('repo', format='knit')
1030
bundle.install_revisions(repo)
1032
bundle = read_bundle(self.create_bundle_text('null:', 'rev2')[0])
1033
self.assertRaises(errors.IncompatibleRevision,
1034
bundle.install_revisions, repo)
1036
def test_get_merge_request(self):
1037
tree = self.make_simple_tree()
1038
tree.commit('hello', rev_id='rev1')
1039
tree.commit('hello', rev_id='rev2')
1040
bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1041
result = bundle.get_merge_request(tree.branch.repository)
1042
self.assertEqual((None, 'rev1', 'inapplicable'), result)
1044
def test_with_subtree(self):
1045
tree = self.make_branch_and_tree('tree',
1046
format='dirstate-with-subtree')
1047
self.b1 = tree.branch
1048
subtree = self.make_branch_and_tree('tree/subtree',
1049
format='dirstate-with-subtree')
1051
tree.commit('hello', rev_id='rev1')
1053
bundle = read_bundle(self.create_bundle_text('null:', 'rev1')[0])
1054
except errors.IncompatibleBundleFormat:
1055
raise TestSkipped("Format 0.8 doesn't work with knit3")
1056
if isinstance(bundle, v09.BundleInfo09):
1057
raise TestSkipped("Format 0.9 doesn't work with subtrees")
1058
repo = self.make_repository('repo', format='knit')
1059
self.assertRaises(errors.IncompatibleRevision,
1060
bundle.install_revisions, repo)
1061
repo2 = self.make_repository('repo2', format='dirstate-with-subtree')
1062
bundle.install_revisions(repo2)
1064
def test_revision_id_with_slash(self):
1065
self.tree1 = self.make_branch_and_tree('tree')
1066
self.b1 = self.tree1.branch
1068
self.tree1.commit('Revision/id/with/slashes', rev_id='rev/id')
1070
raise TestSkipped("Repository doesn't support revision ids with"
1072
bundle = self.get_valid_bundle('null:', 'rev/id')
1074
def test_skip_file(self):
1075
"""Make sure we don't accidentally write to the wrong versionedfile"""
1076
self.tree1 = self.make_branch_and_tree('tree')
1077
self.b1 = self.tree1.branch
1078
# rev1 is not present in bundle, done by fetch
1079
self.build_tree_contents([('tree/file2', 'contents1')])
1080
self.tree1.add('file2', 'file2-id')
1081
self.tree1.commit('rev1', rev_id='reva')
1082
self.build_tree_contents([('tree/file3', 'contents2')])
1083
# rev2 is present in bundle, and done by fetch
1084
# having file1 in the bunle causes file1's versionedfile to be opened.
1085
self.tree1.add('file3', 'file3-id')
1086
self.tree1.commit('rev2')
1087
# Updating file2 should not cause an attempt to add to file1's vf
1088
target = self.tree1.bzrdir.sprout('target').open_workingtree()
1089
self.build_tree_contents([('tree/file2', 'contents3')])
1090
self.tree1.commit('rev3', rev_id='rev3')
1091
bundle = self.get_valid_bundle('reva', 'rev3')
1092
if getattr(bundle, 'get_bundle_reader', None) is None:
1093
raise TestSkipped('Bundle format cannot provide reader')
1094
# be sure that file1 comes before file2
1095
for b, m, k, r, f in bundle.get_bundle_reader().iter_records():
1098
self.assertNotEqual(f, 'file2-id')
1099
bundle.install_revisions(target.branch.repository)
1102
class V08BundleTester(BundleTester, TestCaseWithTransport):
1106
def test_bundle_empty_property(self):
1107
"""Test serializing revision properties with an empty value."""
1108
tree = self.make_branch_and_memory_tree('tree')
1110
self.addCleanup(tree.unlock)
1111
tree.add([''], ['TREE_ROOT'])
1112
tree.commit('One', revprops={'one':'two', 'empty':''}, rev_id='rev1')
1113
self.b1 = tree.branch
1114
bundle_sio, revision_ids = self.create_bundle_text('null:', 'rev1')
917
1115
self.assertContainsRe(bundle_sio.getvalue(),
918
1116
'# properties:\n'
919
1117
'# branch-nick: tree\n'
1039
class MungedBundleTester(TestCaseWithTransport):
1241
class V4BundleTester(BundleTester, TestCaseWithTransport):
1245
def get_valid_bundle(self, base_rev_id, rev_id, checkout_dir=None):
1246
"""Create a bundle from base_rev_id -> rev_id in built-in branch.
1247
Make sure that the text generated is valid, and that it
1248
can be applied against the base, and generate the same information.
1250
:return: The in-memory bundle
1252
bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1254
# This should also validate the generated bundle
1255
bundle = read_bundle(bundle_txt)
1256
repository = self.b1.repository
1257
for bundle_rev in bundle.real_revisions:
1258
# These really should have already been checked when we read the
1259
# bundle, since it computes the sha1 hash for the revision, which
1260
# only will match if everything is okay, but lets be explicit about
1262
branch_rev = repository.get_revision(bundle_rev.revision_id)
1263
for a in ('inventory_sha1', 'revision_id', 'parent_ids',
1264
'timestamp', 'timezone', 'message', 'committer',
1265
'parent_ids', 'properties'):
1266
self.assertEqual(getattr(branch_rev, a),
1267
getattr(bundle_rev, a))
1268
self.assertEqual(len(branch_rev.parent_ids),
1269
len(bundle_rev.parent_ids))
1270
self.assertEqual(set(rev_ids),
1271
set([r.revision_id for r in bundle.real_revisions]))
1272
self.valid_apply_bundle(base_rev_id, bundle,
1273
checkout_dir=checkout_dir)
1277
def get_invalid_bundle(self, base_rev_id, rev_id):
1278
"""Create a bundle from base_rev_id -> rev_id in built-in branch.
1279
Munge the text so that it's invalid.
1281
:return: The in-memory bundle
1283
from bzrlib.bundle import serializer
1284
bundle_txt, rev_ids = self.create_bundle_text(base_rev_id, rev_id)
1285
new_text = self.get_raw(StringIO(''.join(bundle_txt)))
1286
new_text = new_text.replace('<file file_id="exe-1"',
1287
'<file executable="y" file_id="exe-1"')
1288
new_text = new_text.replace('B222', 'B237')
1289
bundle_txt = StringIO()
1290
bundle_txt.write(serializer._get_bundle_header('4'))
1291
bundle_txt.write('\n')
1292
bundle_txt.write(new_text.encode('bz2'))
1294
bundle = read_bundle(bundle_txt)
1295
self.valid_apply_bundle(base_rev_id, bundle)
1298
def create_bundle_text(self, base_rev_id, rev_id):
1299
bundle_txt = StringIO()
1300
rev_ids = write_bundle(self.b1.repository, rev_id, base_rev_id,
1301
bundle_txt, format=self.format)
1303
self.assertEqual(bundle_txt.readline(),
1304
'# Bazaar revision bundle v%s\n' % self.format)
1305
self.assertEqual(bundle_txt.readline(), '#\n')
1306
rev = self.b1.repository.get_revision(rev_id)
1308
return bundle_txt, rev_ids
1310
def get_bundle_tree(self, bundle, revision_id):
1311
repository = self.make_repository('repo')
1312
bundle.install_revisions(repository)
1313
return repository.revision_tree(revision_id)
1315
def test_creation(self):
1316
tree = self.make_branch_and_tree('tree')
1317
self.build_tree_contents([('tree/file', 'contents1\nstatic\n')])
1318
tree.add('file', 'fileid-2')
1319
tree.commit('added file', rev_id='rev1')
1320
self.build_tree_contents([('tree/file', 'contents2\nstatic\n')])
1321
tree.commit('changed file', rev_id='rev2')
1323
serializer = BundleSerializerV4('1.0')
1324
serializer.write(tree.branch.repository, ['rev1', 'rev2'], {}, s)
1326
tree2 = self.make_branch_and_tree('target')
1327
target_repo = tree2.branch.repository
1328
install_bundle(target_repo, serializer.read(s))
1329
vf = target_repo.weave_store.get_weave('fileid-2',
1330
target_repo.get_transaction())
1331
self.assertEqual('contents1\nstatic\n', vf.get_text('rev1'))
1332
self.assertEqual('contents2\nstatic\n', vf.get_text('rev2'))
1333
rtree = target_repo.revision_tree('rev2')
1334
inventory_vf = target_repo.get_inventory_weave()
1335
self.assertEqual(['rev1'], inventory_vf.get_parents('rev2'))
1336
self.assertEqual('changed file',
1337
target_repo.get_revision('rev2').message)
1340
def get_raw(bundle_file):
1342
line = bundle_file.readline()
1343
line = bundle_file.readline()
1344
lines = bundle_file.readlines()
1345
return ''.join(lines).decode('bz2')
1347
def test_copy_signatures(self):
1348
tree_a = self.make_branch_and_tree('tree_a')
1350
import bzrlib.commit as commit
1351
oldstrategy = bzrlib.gpg.GPGStrategy
1352
branch = tree_a.branch
1353
repo_a = branch.repository
1354
tree_a.commit("base", allow_pointless=True, rev_id='A')
1355
self.failIf(branch.repository.has_signature_for_revision_id('A'))
1357
from bzrlib.testament import Testament
1358
# monkey patch gpg signing mechanism
1359
bzrlib.gpg.GPGStrategy = bzrlib.gpg.LoopbackGPGStrategy
1360
new_config = test_commit.MustSignConfig(branch)
1361
commit.Commit(config=new_config).commit(message="base",
1362
allow_pointless=True,
1364
working_tree=tree_a)
1366
return bzrlib.gpg.LoopbackGPGStrategy(None).sign(text)
1367
self.assertTrue(repo_a.has_signature_for_revision_id('B'))
1369
bzrlib.gpg.GPGStrategy = oldstrategy
1370
tree_b = self.make_branch_and_tree('tree_b')
1371
repo_b = tree_b.branch.repository
1373
serializer = BundleSerializerV4('4')
1374
serializer.write(tree_a.branch.repository, ['A', 'B'], {}, s)
1376
install_bundle(repo_b, serializer.read(s))
1377
self.assertTrue(repo_b.has_signature_for_revision_id('B'))
1378
self.assertEqual(repo_b.get_signature_text('B'),
1379
repo_a.get_signature_text('B'))
1381
# ensure repeat installs are harmless
1382
install_bundle(repo_b, serializer.read(s))
1385
class V4WeaveBundleTester(V4BundleTester):
1387
def bzrdir_format(self):
1391
class MungedBundleTester(object):
1041
1393
def build_test_bundle(self):
1042
1394
wt = self.make_branch_and_tree('b1')
1120
1477
bundle = read_bundle(bundle_txt)
1121
1478
self.check_valid(bundle)
1481
class MungedBundleTesterV4(TestCaseWithTransport, MungedBundleTester):
1486
class TestBundleWriterReader(TestCase):
1488
def test_roundtrip_record(self):
1489
fileobj = StringIO()
1490
writer = v4.BundleWriter(fileobj)
1492
writer.add_info_record(foo='bar')
1493
writer._add_record("Record body", {'parents': ['1', '3'],
1494
'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
1497
reader = v4.BundleReader(fileobj, stream_input=True)
1498
record_iter = reader.iter_records()
1499
record = record_iter.next()
1500
self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1501
'info', None, None), record)
1502
record = record_iter.next()
1503
self.assertEqual(("Record body", {'storage_kind': 'fulltext',
1504
'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
1507
def test_roundtrip_record_memory_hungry(self):
1508
fileobj = StringIO()
1509
writer = v4.BundleWriter(fileobj)
1511
writer.add_info_record(foo='bar')
1512
writer._add_record("Record body", {'parents': ['1', '3'],
1513
'storage_kind':'fulltext'}, 'file', 'revid', 'fileid')
1516
reader = v4.BundleReader(fileobj, stream_input=False)
1517
record_iter = reader.iter_records()
1518
record = record_iter.next()
1519
self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1520
'info', None, None), record)
1521
record = record_iter.next()
1522
self.assertEqual(("Record body", {'storage_kind': 'fulltext',
1523
'parents': ['1', '3']}, 'file', 'revid', 'fileid'),
1526
def test_encode_name(self):
1527
self.assertEqual('revision/rev1',
1528
v4.BundleWriter.encode_name('revision', 'rev1'))
1529
self.assertEqual('file/rev//1/file-id-1',
1530
v4.BundleWriter.encode_name('file', 'rev/1', 'file-id-1'))
1531
self.assertEqual('info',
1532
v4.BundleWriter.encode_name('info', None, None))
1534
def test_decode_name(self):
1535
self.assertEqual(('revision', 'rev1', None),
1536
v4.BundleReader.decode_name('revision/rev1'))
1537
self.assertEqual(('file', 'rev/1', 'file-id-1'),
1538
v4.BundleReader.decode_name('file/rev//1/file-id-1'))
1539
self.assertEqual(('info', None, None),
1540
v4.BundleReader.decode_name('info'))
1542
def test_too_many_names(self):
1543
fileobj = StringIO()
1544
writer = v4.BundleWriter(fileobj)
1546
writer.add_info_record(foo='bar')
1547
writer._container.add_bytes_record('blah', ['two', 'names'])
1550
record_iter = v4.BundleReader(fileobj).iter_records()
1551
record = record_iter.next()
1552
self.assertEqual((None, {'foo': 'bar', 'storage_kind': 'header'},
1553
'info', None, None), record)
1554
self.assertRaises(BadBundle, record_iter.next)