916
508
transform, root = self.get_transform()
917
509
wt = transform._tree
919
self.addCleanup(wt.unlock)
920
510
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
922
sac = transform.new_file('set_after_creation', root,
923
'Set after creation', 'sac')
512
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
924
513
transform.set_executability(True, sac)
925
uws = transform.new_file('unset_without_set', root, 'Unset badly',
514
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
927
515
self.assertRaises(KeyError, transform.set_executability, None, uws)
928
516
transform.apply()
929
517
self.assertTrue(wt.is_executable('soc'))
930
518
self.assertTrue(wt.is_executable('sac'))
932
def test_preserve_mode(self):
933
"""File mode is preserved when replacing content"""
934
if sys.platform == 'win32':
935
raise TestSkipped('chmod has no effect on win32')
936
transform, root = self.get_transform()
937
transform.new_file('file1', root, 'contents', 'file1-id', True)
940
self.addCleanup(self.wt.unlock)
941
self.assertTrue(self.wt.is_executable('file1-id'))
942
transform, root = self.get_transform()
943
file1_id = transform.trans_id_tree_file_id('file1-id')
944
transform.delete_contents(file1_id)
945
transform.create_file('contents2', file1_id)
947
self.assertTrue(self.wt.is_executable('file1-id'))
949
def test__set_mode_stats_correctly(self):
950
"""_set_mode stats to determine file mode."""
951
if sys.platform == 'win32':
952
raise TestSkipped('chmod has no effect on win32')
956
def instrumented_stat(path):
957
stat_paths.append(path)
958
return real_stat(path)
960
transform, root = self.get_transform()
962
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
963
file_id='bar-id-1', executable=False)
966
transform, root = self.get_transform()
967
bar1_id = transform.trans_id_tree_path('bar')
968
bar2_id = transform.trans_id_tree_path('bar2')
970
os.stat = instrumented_stat
971
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
976
bar1_abspath = self.wt.abspath('bar')
977
self.assertEqual([bar1_abspath], stat_paths)
979
def test_iter_changes(self):
980
self.wt.set_root_id('eert_toor')
981
transform, root = self.get_transform()
982
transform.new_file('old', root, 'blah', 'id-1', True)
984
transform, root = self.get_transform()
986
self.assertEqual([], list(transform.iter_changes()))
987
old = transform.trans_id_tree_file_id('id-1')
988
transform.unversion_file(old)
989
self.assertEqual([('id-1', ('old', None), False, (True, False),
990
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
991
(True, True))], list(transform.iter_changes()))
992
transform.new_directory('new', root, 'id-1')
993
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
994
('eert_toor', 'eert_toor'), ('old', 'new'),
995
('file', 'directory'),
996
(True, False))], list(transform.iter_changes()))
1000
def test_iter_changes_new(self):
1001
self.wt.set_root_id('eert_toor')
1002
transform, root = self.get_transform()
1003
transform.new_file('old', root, 'blah')
1005
transform, root = self.get_transform()
1007
old = transform.trans_id_tree_path('old')
1008
transform.version_file('id-1', old)
1009
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
1010
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1011
(False, False))], list(transform.iter_changes()))
1013
transform.finalize()
1015
def test_iter_changes_modifications(self):
1016
self.wt.set_root_id('eert_toor')
1017
transform, root = self.get_transform()
1018
transform.new_file('old', root, 'blah', 'id-1')
1019
transform.new_file('new', root, 'blah')
1020
transform.new_directory('subdir', root, 'subdir-id')
1022
transform, root = self.get_transform()
1024
old = transform.trans_id_tree_path('old')
1025
subdir = transform.trans_id_tree_file_id('subdir-id')
1026
new = transform.trans_id_tree_path('new')
1027
self.assertEqual([], list(transform.iter_changes()))
1030
transform.delete_contents(old)
1031
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1032
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1033
(False, False))], list(transform.iter_changes()))
1036
transform.create_file('blah', old)
1037
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1038
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1039
(False, False))], list(transform.iter_changes()))
1040
transform.cancel_deletion(old)
1041
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1042
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1043
(False, False))], list(transform.iter_changes()))
1044
transform.cancel_creation(old)
1046
# move file_id to a different file
1047
self.assertEqual([], list(transform.iter_changes()))
1048
transform.unversion_file(old)
1049
transform.version_file('id-1', new)
1050
transform.adjust_path('old', root, new)
1051
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1052
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1053
(False, False))], list(transform.iter_changes()))
1054
transform.cancel_versioning(new)
1055
transform._removed_id = set()
1058
self.assertEqual([], list(transform.iter_changes()))
1059
transform.set_executability(True, old)
1060
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1061
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1062
(False, True))], list(transform.iter_changes()))
1063
transform.set_executability(None, old)
1066
self.assertEqual([], list(transform.iter_changes()))
1067
transform.adjust_path('new', root, old)
1068
transform._new_parent = {}
1069
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1070
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1071
(False, False))], list(transform.iter_changes()))
1072
transform._new_name = {}
1075
self.assertEqual([], list(transform.iter_changes()))
1076
transform.adjust_path('new', subdir, old)
1077
transform._new_name = {}
1078
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1079
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1080
('file', 'file'), (False, False))],
1081
list(transform.iter_changes()))
1082
transform._new_path = {}
1085
transform.finalize()
1087
def test_iter_changes_modified_bleed(self):
1088
self.wt.set_root_id('eert_toor')
1089
"""Modified flag should not bleed from one change to another"""
1090
# unfortunately, we have no guarantee that file1 (which is modified)
1091
# will be applied before file2. And if it's applied after file2, it
1092
# obviously can't bleed into file2's change output. But for now, it
1094
transform, root = self.get_transform()
1095
transform.new_file('file1', root, 'blah', 'id-1')
1096
transform.new_file('file2', root, 'blah', 'id-2')
1098
transform, root = self.get_transform()
1100
transform.delete_contents(transform.trans_id_file_id('id-1'))
1101
transform.set_executability(True,
1102
transform.trans_id_file_id('id-2'))
1103
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1104
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1105
('file', None), (False, False)),
1106
('id-2', (u'file2', u'file2'), False, (True, True),
1107
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1108
('file', 'file'), (False, True))],
1109
list(transform.iter_changes()))
1111
transform.finalize()
1113
def test_iter_changes_move_missing(self):
1114
"""Test moving ids with no files around"""
1115
self.wt.set_root_id('toor_eert')
1116
# Need two steps because versioning a non-existant file is a conflict.
1117
transform, root = self.get_transform()
1118
transform.new_directory('floater', root, 'floater-id')
1120
transform, root = self.get_transform()
1121
transform.delete_contents(transform.trans_id_tree_path('floater'))
1123
transform, root = self.get_transform()
1124
floater = transform.trans_id_tree_path('floater')
1126
transform.adjust_path('flitter', root, floater)
1127
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1128
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1129
(None, None), (False, False))], list(transform.iter_changes()))
1131
transform.finalize()
1133
def test_iter_changes_pointless(self):
1134
"""Ensure that no-ops are not treated as modifications"""
1135
self.wt.set_root_id('eert_toor')
1136
transform, root = self.get_transform()
1137
transform.new_file('old', root, 'blah', 'id-1')
1138
transform.new_directory('subdir', root, 'subdir-id')
1140
transform, root = self.get_transform()
1142
old = transform.trans_id_tree_path('old')
1143
subdir = transform.trans_id_tree_file_id('subdir-id')
1144
self.assertEqual([], list(transform.iter_changes()))
1145
transform.delete_contents(subdir)
1146
transform.create_directory(subdir)
1147
transform.set_executability(False, old)
1148
transform.unversion_file(old)
1149
transform.version_file('id-1', old)
1150
transform.adjust_path('old', root, old)
1151
self.assertEqual([], list(transform.iter_changes()))
1153
transform.finalize()
1155
def test_rename_count(self):
1156
transform, root = self.get_transform()
1157
transform.new_file('name1', root, 'contents')
1158
self.assertEqual(transform.rename_count, 0)
1160
self.assertEqual(transform.rename_count, 1)
1161
transform2, root = self.get_transform()
1162
transform2.adjust_path('name2', root,
1163
transform2.trans_id_tree_path('name1'))
1164
self.assertEqual(transform2.rename_count, 0)
1166
self.assertEqual(transform2.rename_count, 2)
1168
def test_change_parent(self):
1169
"""Ensure that after we change a parent, the results are still right.
1171
Renames and parent changes on pending transforms can happen as part
1172
of conflict resolution, and are explicitly permitted by the
1175
This test ensures they work correctly with the rename-avoidance
1178
transform, root = self.get_transform()
1179
parent1 = transform.new_directory('parent1', root)
1180
child1 = transform.new_file('child1', parent1, 'contents')
1181
parent2 = transform.new_directory('parent2', root)
1182
transform.adjust_path('child1', parent2, child1)
1184
self.failIfExists(self.wt.abspath('parent1/child1'))
1185
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1186
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1187
# no rename for child1 (counting only renames during apply)
1188
self.failUnlessEqual(2, transform.rename_count)
1190
def test_cancel_parent(self):
1191
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1193
This is like the test_change_parent, except that we cancel the parent
1194
before adjusting the path. The transform must detect that the
1195
directory is non-empty, and move children to safe locations.
1197
transform, root = self.get_transform()
1198
parent1 = transform.new_directory('parent1', root)
1199
child1 = transform.new_file('child1', parent1, 'contents')
1200
child2 = transform.new_file('child2', parent1, 'contents')
1202
transform.cancel_creation(parent1)
1204
self.fail('Failed to move child1 before deleting parent1')
1205
transform.cancel_creation(child2)
1206
transform.create_directory(parent1)
1208
transform.cancel_creation(parent1)
1209
# If the transform incorrectly believes that child2 is still in
1210
# parent1's limbo directory, it will try to rename it and fail
1211
# because was already moved by the first cancel_creation.
1213
self.fail('Transform still thinks child2 is a child of parent1')
1214
parent2 = transform.new_directory('parent2', root)
1215
transform.adjust_path('child1', parent2, child1)
1217
self.failIfExists(self.wt.abspath('parent1'))
1218
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1219
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1220
self.failUnlessEqual(2, transform.rename_count)
1222
def test_adjust_and_cancel(self):
1223
"""Make sure adjust_path keeps track of limbo children properly"""
1224
transform, root = self.get_transform()
1225
parent1 = transform.new_directory('parent1', root)
1226
child1 = transform.new_file('child1', parent1, 'contents')
1227
parent2 = transform.new_directory('parent2', root)
1228
transform.adjust_path('child1', parent2, child1)
1229
transform.cancel_creation(child1)
1231
transform.cancel_creation(parent1)
1232
# if the transform thinks child1 is still in parent1's limbo
1233
# directory, it will attempt to move it and fail.
1235
self.fail('Transform still thinks child1 is a child of parent1')
1236
transform.finalize()
1238
def test_noname_contents(self):
1239
"""TreeTransform should permit deferring naming files."""
1240
transform, root = self.get_transform()
1241
parent = transform.trans_id_file_id('parent-id')
1243
transform.create_directory(parent)
1245
self.fail("Can't handle contents with no name")
1246
transform.finalize()
1248
def test_noname_contents_nested(self):
1249
"""TreeTransform should permit deferring naming files."""
1250
transform, root = self.get_transform()
1251
parent = transform.trans_id_file_id('parent-id')
1253
transform.create_directory(parent)
1255
self.fail("Can't handle contents with no name")
1256
child = transform.new_directory('child', parent)
1257
transform.adjust_path('parent', root, parent)
1259
self.failUnlessExists(self.wt.abspath('parent/child'))
1260
self.assertEqual(1, transform.rename_count)
1262
def test_reuse_name(self):
1263
"""Avoid reusing the same limbo name for different files"""
1264
transform, root = self.get_transform()
1265
parent = transform.new_directory('parent', root)
1266
child1 = transform.new_directory('child', parent)
1268
child2 = transform.new_directory('child', parent)
1270
self.fail('Tranform tried to use the same limbo name twice')
1271
transform.adjust_path('child2', parent, child2)
1273
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1274
# child2 is put into top-level limbo because child1 has already
1275
# claimed the direct limbo path when child2 is created. There is no
1276
# advantage in renaming files once they're in top-level limbo, except
1278
self.assertEqual(2, transform.rename_count)
1280
def test_reuse_when_first_moved(self):
1281
"""Don't avoid direct paths when it is safe to use them"""
1282
transform, root = self.get_transform()
1283
parent = transform.new_directory('parent', root)
1284
child1 = transform.new_directory('child', parent)
1285
transform.adjust_path('child1', parent, child1)
1286
child2 = transform.new_directory('child', parent)
1288
# limbo/new-1 => parent
1289
self.assertEqual(1, transform.rename_count)
1291
def test_reuse_after_cancel(self):
1292
"""Don't avoid direct paths when it is safe to use them"""
1293
transform, root = self.get_transform()
1294
parent2 = transform.new_directory('parent2', root)
1295
child1 = transform.new_directory('child1', parent2)
1296
transform.cancel_creation(parent2)
1297
transform.create_directory(parent2)
1298
child2 = transform.new_directory('child1', parent2)
1299
transform.adjust_path('child2', parent2, child1)
1301
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1302
self.assertEqual(2, transform.rename_count)
1304
def test_finalize_order(self):
1305
"""Finalize must be done in child-to-parent order"""
1306
transform, root = self.get_transform()
1307
parent = transform.new_directory('parent', root)
1308
child = transform.new_directory('child', parent)
1310
transform.finalize()
1312
self.fail('Tried to remove parent before child1')
1314
def test_cancel_with_cancelled_child_should_succeed(self):
1315
transform, root = self.get_transform()
1316
parent = transform.new_directory('parent', root)
1317
child = transform.new_directory('child', parent)
1318
transform.cancel_creation(child)
1319
transform.cancel_creation(parent)
1320
transform.finalize()
1322
def test_rollback_on_directory_clash(self):
1324
wt = self.make_branch_and_tree('.')
1325
tt = TreeTransform(wt) # TreeTransform obtains write lock
1327
foo = tt.new_directory('foo', tt.root)
1328
tt.new_file('bar', foo, 'foobar')
1329
baz = tt.new_directory('baz', tt.root)
1330
tt.new_file('qux', baz, 'quux')
1331
# Ask for a rename 'foo' -> 'baz'
1332
tt.adjust_path('baz', tt.root, foo)
1333
# Lie to tt that we've already resolved all conflicts.
1334
tt.apply(no_conflicts=True)
1338
# The rename will fail because the target directory is not empty (but
1339
# raises FileExists anyway).
1340
err = self.assertRaises(errors.FileExists, tt_helper)
1341
self.assertContainsRe(str(err),
1342
"^File exists: .+/baz")
1344
def test_two_directories_clash(self):
1346
wt = self.make_branch_and_tree('.')
1347
tt = TreeTransform(wt) # TreeTransform obtains write lock
1349
foo_1 = tt.new_directory('foo', tt.root)
1350
tt.new_directory('bar', foo_1)
1351
# Adding the same directory with a different content
1352
foo_2 = tt.new_directory('foo', tt.root)
1353
tt.new_directory('baz', foo_2)
1354
# Lie to tt that we've already resolved all conflicts.
1355
tt.apply(no_conflicts=True)
1359
err = self.assertRaises(errors.FileExists, tt_helper)
1360
self.assertContainsRe(str(err),
1361
"^File exists: .+/foo")
1363
def test_two_directories_clash_finalize(self):
1365
wt = self.make_branch_and_tree('.')
1366
tt = TreeTransform(wt) # TreeTransform obtains write lock
1368
foo_1 = tt.new_directory('foo', tt.root)
1369
tt.new_directory('bar', foo_1)
1370
# Adding the same directory with a different content
1371
foo_2 = tt.new_directory('foo', tt.root)
1372
tt.new_directory('baz', foo_2)
1373
# Lie to tt that we've already resolved all conflicts.
1374
tt.apply(no_conflicts=True)
1378
err = self.assertRaises(errors.FileExists, tt_helper)
1379
self.assertContainsRe(str(err),
1380
"^File exists: .+/foo")
1382
def test_file_to_directory(self):
1383
wt = self.make_branch_and_tree('.')
1384
self.build_tree(['foo'])
1387
tt = TreeTransform(wt)
1388
self.addCleanup(tt.finalize)
1389
foo_trans_id = tt.trans_id_tree_path("foo")
1390
tt.delete_contents(foo_trans_id)
1391
tt.create_directory(foo_trans_id)
1392
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1393
tt.create_file(["aa\n"], bar_trans_id)
1394
tt.version_file("bar-1", bar_trans_id)
1396
self.failUnlessExists("foo/bar")
1399
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1404
changes = wt.changes_from(wt.basis_tree())
1405
self.assertFalse(changes.has_changed(), changes)
1407
def test_file_to_symlink(self):
1408
self.requireFeature(SymlinkFeature)
1409
wt = self.make_branch_and_tree('.')
1410
self.build_tree(['foo'])
1413
tt = TreeTransform(wt)
1414
self.addCleanup(tt.finalize)
1415
foo_trans_id = tt.trans_id_tree_path("foo")
1416
tt.delete_contents(foo_trans_id)
1417
tt.create_symlink("bar", foo_trans_id)
1419
self.failUnlessExists("foo")
1421
self.addCleanup(wt.unlock)
1422
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1425
def test_dir_to_file(self):
1426
wt = self.make_branch_and_tree('.')
1427
self.build_tree(['foo/', 'foo/bar'])
1428
wt.add(['foo', 'foo/bar'])
1430
tt = TreeTransform(wt)
1431
self.addCleanup(tt.finalize)
1432
foo_trans_id = tt.trans_id_tree_path("foo")
1433
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1434
tt.delete_contents(foo_trans_id)
1435
tt.delete_versioned(bar_trans_id)
1436
tt.create_file(["aa\n"], foo_trans_id)
1438
self.failUnlessExists("foo")
1440
self.addCleanup(wt.unlock)
1441
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1444
def test_dir_to_hardlink(self):
1445
self.requireFeature(HardlinkFeature)
1446
wt = self.make_branch_and_tree('.')
1447
self.build_tree(['foo/', 'foo/bar'])
1448
wt.add(['foo', 'foo/bar'])
1450
tt = TreeTransform(wt)
1451
self.addCleanup(tt.finalize)
1452
foo_trans_id = tt.trans_id_tree_path("foo")
1453
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1454
tt.delete_contents(foo_trans_id)
1455
tt.delete_versioned(bar_trans_id)
1456
self.build_tree(['baz'])
1457
tt.create_hardlink("baz", foo_trans_id)
1459
self.failUnlessExists("foo")
1460
self.failUnlessExists("baz")
1462
self.addCleanup(wt.unlock)
1463
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1466
def test_no_final_path(self):
1467
transform, root = self.get_transform()
1468
trans_id = transform.trans_id_file_id('foo')
1469
transform.create_file('bar', trans_id)
1470
transform.cancel_creation(trans_id)
1473
def test_create_from_tree(self):
1474
tree1 = self.make_branch_and_tree('tree1')
1475
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1476
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1477
tree2 = self.make_branch_and_tree('tree2')
1478
tt = TreeTransform(tree2)
1479
foo_trans_id = tt.create_path('foo', tt.root)
1480
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1481
bar_trans_id = tt.create_path('bar', tt.root)
1482
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1484
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1485
self.assertFileEqual('baz', 'tree2/bar')
1487
def test_create_from_tree_bytes(self):
1488
"""Provided lines are used instead of tree content."""
1489
tree1 = self.make_branch_and_tree('tree1')
1490
self.build_tree_contents([('tree1/foo', 'bar'),])
1491
tree1.add('foo', 'foo-id')
1492
tree2 = self.make_branch_and_tree('tree2')
1493
tt = TreeTransform(tree2)
1494
foo_trans_id = tt.create_path('foo', tt.root)
1495
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1497
self.assertFileEqual('qux', 'tree2/foo')
1499
def test_create_from_tree_symlink(self):
1500
self.requireFeature(SymlinkFeature)
1501
tree1 = self.make_branch_and_tree('tree1')
1502
os.symlink('bar', 'tree1/foo')
1503
tree1.add('foo', 'foo-id')
1504
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1505
foo_trans_id = tt.create_path('foo', tt.root)
1506
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1508
self.assertEqual('bar', os.readlink('tree2/foo'))
1511
521
class TransformGroup(object):
1513
def __init__(self, dirname, root_id):
522
def __init__(self, dirname):
1514
523
self.name = dirname
1515
524
os.mkdir(dirname)
1516
525
self.wt = BzrDir.create_standalone_workingtree(dirname)
1517
self.wt.set_root_id(root_id)
1518
526
self.b = self.wt.branch
1519
527
self.tt = TreeTransform(self.wt)
1520
528
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1523
530
def conflict_text(tree, merge):
1524
531
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1525
532
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1528
535
class TestTransformMerge(TestCaseInTempDir):
1530
536
def test_text_merge(self):
1531
root_id = generate_ids.gen_root_id()
1532
base = TransformGroup("base", root_id)
537
base = TransformGroup("base")
1533
538
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1534
539
base.tt.new_file('b', base.root, 'b1', 'b')
1535
540
base.tt.new_file('c', base.root, 'c', 'c')
1724
725
a.add(['foo', 'foo/bar', 'foo/baz'])
1725
726
a.commit('initial commit')
1726
727
b = BzrDir.create_standalone_workingtree('b')
1727
basis = a.basis_tree()
1729
self.addCleanup(basis.unlock)
1730
build_tree(basis, b)
728
build_tree(a.basis_tree(), b)
1731
729
self.assertIs(os.path.isdir('b/foo'), True)
1732
730
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1733
731
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1735
def test_build_with_references(self):
1736
tree = self.make_branch_and_tree('source',
1737
format='dirstate-with-subtree')
1738
subtree = self.make_branch_and_tree('source/subtree',
1739
format='dirstate-with-subtree')
1740
tree.add_reference(subtree)
1741
tree.commit('a revision')
1742
tree.branch.create_checkout('target')
1743
self.failUnlessExists('target')
1744
self.failUnlessExists('target/subtree')
1746
def test_file_conflict_handling(self):
1747
"""Ensure that when building trees, conflict handling is done"""
1748
source = self.make_branch_and_tree('source')
1749
target = self.make_branch_and_tree('target')
1750
self.build_tree(['source/file', 'target/file'])
1751
source.add('file', 'new-file')
1752
source.commit('added file')
1753
build_tree(source.basis_tree(), target)
1754
self.assertEqual([DuplicateEntry('Moved existing file to',
1755
'file.moved', 'file', None, 'new-file')],
1757
target2 = self.make_branch_and_tree('target2')
1758
target_file = file('target2/file', 'wb')
1760
source_file = file('source/file', 'rb')
1762
target_file.write(source_file.read())
1767
build_tree(source.basis_tree(), target2)
1768
self.assertEqual([], target2.conflicts())
1770
def test_symlink_conflict_handling(self):
1771
"""Ensure that when building trees, conflict handling is done"""
1772
self.requireFeature(SymlinkFeature)
1773
source = self.make_branch_and_tree('source')
1774
os.symlink('foo', 'source/symlink')
1775
source.add('symlink', 'new-symlink')
1776
source.commit('added file')
1777
target = self.make_branch_and_tree('target')
1778
os.symlink('bar', 'target/symlink')
1779
build_tree(source.basis_tree(), target)
1780
self.assertEqual([DuplicateEntry('Moved existing file to',
1781
'symlink.moved', 'symlink', None, 'new-symlink')],
1783
target = self.make_branch_and_tree('target2')
1784
os.symlink('foo', 'target2/symlink')
1785
build_tree(source.basis_tree(), target)
1786
self.assertEqual([], target.conflicts())
1788
def test_directory_conflict_handling(self):
1789
"""Ensure that when building trees, conflict handling is done"""
1790
source = self.make_branch_and_tree('source')
1791
target = self.make_branch_and_tree('target')
1792
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1793
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1794
source.commit('added file')
1795
build_tree(source.basis_tree(), target)
1796
self.assertEqual([], target.conflicts())
1797
self.failUnlessExists('target/dir1/file')
1799
# Ensure contents are merged
1800
target = self.make_branch_and_tree('target2')
1801
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1802
build_tree(source.basis_tree(), target)
1803
self.assertEqual([], target.conflicts())
1804
self.failUnlessExists('target2/dir1/file2')
1805
self.failUnlessExists('target2/dir1/file')
1807
# Ensure new contents are suppressed for existing branches
1808
target = self.make_branch_and_tree('target3')
1809
self.make_branch('target3/dir1')
1810
self.build_tree(['target3/dir1/file2'])
1811
build_tree(source.basis_tree(), target)
1812
self.failIfExists('target3/dir1/file')
1813
self.failUnlessExists('target3/dir1/file2')
1814
self.failUnlessExists('target3/dir1.diverted/file')
1815
self.assertEqual([DuplicateEntry('Diverted to',
1816
'dir1.diverted', 'dir1', 'new-dir1', None)],
1819
target = self.make_branch_and_tree('target4')
1820
self.build_tree(['target4/dir1/'])
1821
self.make_branch('target4/dir1/file')
1822
build_tree(source.basis_tree(), target)
1823
self.failUnlessExists('target4/dir1/file')
1824
self.assertEqual('directory', file_kind('target4/dir1/file'))
1825
self.failUnlessExists('target4/dir1/file.diverted')
1826
self.assertEqual([DuplicateEntry('Diverted to',
1827
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1830
def test_mixed_conflict_handling(self):
1831
"""Ensure that when building trees, conflict handling is done"""
1832
source = self.make_branch_and_tree('source')
1833
target = self.make_branch_and_tree('target')
1834
self.build_tree(['source/name', 'target/name/'])
1835
source.add('name', 'new-name')
1836
source.commit('added file')
1837
build_tree(source.basis_tree(), target)
1838
self.assertEqual([DuplicateEntry('Moved existing file to',
1839
'name.moved', 'name', None, 'new-name')], target.conflicts())
1841
def test_raises_in_populated(self):
1842
source = self.make_branch_and_tree('source')
1843
self.build_tree(['source/name'])
1845
source.commit('added name')
1846
target = self.make_branch_and_tree('target')
1847
self.build_tree(['target/name'])
1849
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1850
build_tree, source.basis_tree(), target)
1852
def test_build_tree_rename_count(self):
1853
source = self.make_branch_and_tree('source')
1854
self.build_tree(['source/file1', 'source/dir1/'])
1855
source.add(['file1', 'dir1'])
1856
source.commit('add1')
1857
target1 = self.make_branch_and_tree('target1')
1858
transform_result = build_tree(source.basis_tree(), target1)
1859
self.assertEqual(2, transform_result.rename_count)
1861
self.build_tree(['source/dir1/file2'])
1862
source.add(['dir1/file2'])
1863
source.commit('add3')
1864
target2 = self.make_branch_and_tree('target2')
1865
transform_result = build_tree(source.basis_tree(), target2)
1866
# children of non-root directories should not be renamed
1867
self.assertEqual(2, transform_result.rename_count)
1869
def create_ab_tree(self):
1870
"""Create a committed test tree with two files"""
1871
source = self.make_branch_and_tree('source')
1872
self.build_tree_contents([('source/file1', 'A')])
1873
self.build_tree_contents([('source/file2', 'B')])
1874
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1875
source.commit('commit files')
1877
self.addCleanup(source.unlock)
1880
def test_build_tree_accelerator_tree(self):
1881
source = self.create_ab_tree()
1882
self.build_tree_contents([('source/file2', 'C')])
1884
real_source_get_file = source.get_file
1885
def get_file(file_id, path=None):
1886
calls.append(file_id)
1887
return real_source_get_file(file_id, path)
1888
source.get_file = get_file
1889
target = self.make_branch_and_tree('target')
1890
revision_tree = source.basis_tree()
1891
revision_tree.lock_read()
1892
self.addCleanup(revision_tree.unlock)
1893
build_tree(revision_tree, target, source)
1894
self.assertEqual(['file1-id'], calls)
1896
self.addCleanup(target.unlock)
1897
self.assertEqual([], list(target.iter_changes(revision_tree)))
1899
def test_build_tree_accelerator_tree_missing_file(self):
1900
source = self.create_ab_tree()
1901
os.unlink('source/file1')
1902
source.remove(['file2'])
1903
target = self.make_branch_and_tree('target')
1904
revision_tree = source.basis_tree()
1905
revision_tree.lock_read()
1906
self.addCleanup(revision_tree.unlock)
1907
build_tree(revision_tree, target, source)
1909
self.addCleanup(target.unlock)
1910
self.assertEqual([], list(target.iter_changes(revision_tree)))
1912
def test_build_tree_accelerator_wrong_kind(self):
1913
self.requireFeature(SymlinkFeature)
1914
source = self.make_branch_and_tree('source')
1915
self.build_tree_contents([('source/file1', '')])
1916
self.build_tree_contents([('source/file2', '')])
1917
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1918
source.commit('commit files')
1919
os.unlink('source/file2')
1920
self.build_tree_contents([('source/file2/', 'C')])
1921
os.unlink('source/file1')
1922
os.symlink('file2', 'source/file1')
1924
real_source_get_file = source.get_file
1925
def get_file(file_id, path=None):
1926
calls.append(file_id)
1927
return real_source_get_file(file_id, path)
1928
source.get_file = get_file
1929
target = self.make_branch_and_tree('target')
1930
revision_tree = source.basis_tree()
1931
revision_tree.lock_read()
1932
self.addCleanup(revision_tree.unlock)
1933
build_tree(revision_tree, target, source)
1934
self.assertEqual([], calls)
1936
self.addCleanup(target.unlock)
1937
self.assertEqual([], list(target.iter_changes(revision_tree)))
1939
def test_build_tree_hardlink(self):
1940
self.requireFeature(HardlinkFeature)
1941
source = self.create_ab_tree()
1942
target = self.make_branch_and_tree('target')
1943
revision_tree = source.basis_tree()
1944
revision_tree.lock_read()
1945
self.addCleanup(revision_tree.unlock)
1946
build_tree(revision_tree, target, source, hardlink=True)
1948
self.addCleanup(target.unlock)
1949
self.assertEqual([], list(target.iter_changes(revision_tree)))
1950
source_stat = os.stat('source/file1')
1951
target_stat = os.stat('target/file1')
1952
self.assertEqual(source_stat, target_stat)
1954
# Explicitly disallowing hardlinks should prevent them.
1955
target2 = self.make_branch_and_tree('target2')
1956
build_tree(revision_tree, target2, source, hardlink=False)
1958
self.addCleanup(target2.unlock)
1959
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1960
source_stat = os.stat('source/file1')
1961
target2_stat = os.stat('target2/file1')
1962
self.assertNotEqual(source_stat, target2_stat)
1964
def test_build_tree_accelerator_tree_moved(self):
1965
source = self.make_branch_and_tree('source')
1966
self.build_tree_contents([('source/file1', 'A')])
1967
source.add(['file1'], ['file1-id'])
1968
source.commit('commit files')
1969
source.rename_one('file1', 'file2')
1971
self.addCleanup(source.unlock)
1972
target = self.make_branch_and_tree('target')
1973
revision_tree = source.basis_tree()
1974
revision_tree.lock_read()
1975
self.addCleanup(revision_tree.unlock)
1976
build_tree(revision_tree, target, source)
1978
self.addCleanup(target.unlock)
1979
self.assertEqual([], list(target.iter_changes(revision_tree)))
1981
def test_build_tree_hardlinks_preserve_execute(self):
1982
self.requireFeature(HardlinkFeature)
1983
source = self.create_ab_tree()
1984
tt = TreeTransform(source)
1985
trans_id = tt.trans_id_tree_file_id('file1-id')
1986
tt.set_executability(True, trans_id)
1988
self.assertTrue(source.is_executable('file1-id'))
1989
target = self.make_branch_and_tree('target')
1990
revision_tree = source.basis_tree()
1991
revision_tree.lock_read()
1992
self.addCleanup(revision_tree.unlock)
1993
build_tree(revision_tree, target, source, hardlink=True)
1995
self.addCleanup(target.unlock)
1996
self.assertEqual([], list(target.iter_changes(revision_tree)))
1997
self.assertTrue(source.is_executable('file1-id'))
1999
def install_rot13_content_filter(self, pattern):
2001
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2002
# below, but that looks a bit... hard to read even if it's exactly
2004
original_registry = filters._reset_registry()
2005
def restore_registry():
2006
filters._reset_registry(original_registry)
2007
self.addCleanup(restore_registry)
2008
def rot13(chunks, context=None):
2009
return [''.join(chunks).encode('rot13')]
2010
rot13filter = filters.ContentFilter(rot13, rot13)
2011
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
2012
os.mkdir(self.test_home_dir + '/.bazaar')
2013
rules_filename = self.test_home_dir + '/.bazaar/rules'
2014
f = open(rules_filename, 'wb')
2015
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2017
def uninstall_rules():
2018
os.remove(rules_filename)
2020
self.addCleanup(uninstall_rules)
2023
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2024
"""build_tree will not hardlink files that have content filtering rules
2025
applied to them (but will still hardlink other files from the same tree
2028
self.requireFeature(HardlinkFeature)
2029
self.install_rot13_content_filter('file1')
2030
source = self.create_ab_tree()
2031
target = self.make_branch_and_tree('target')
2032
revision_tree = source.basis_tree()
2033
revision_tree.lock_read()
2034
self.addCleanup(revision_tree.unlock)
2035
build_tree(revision_tree, target, source, hardlink=True)
2037
self.addCleanup(target.unlock)
2038
self.assertEqual([], list(target.iter_changes(revision_tree)))
2039
source_stat = os.stat('source/file1')
2040
target_stat = os.stat('target/file1')
2041
self.assertNotEqual(source_stat, target_stat)
2042
source_stat = os.stat('source/file2')
2043
target_stat = os.stat('target/file2')
2044
self.assertEqualStat(source_stat, target_stat)
2046
def test_case_insensitive_build_tree_inventory(self):
2047
if (tests.CaseInsensitiveFilesystemFeature.available()
2048
or tests.CaseInsCasePresFilenameFeature.available()):
2049
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2050
source = self.make_branch_and_tree('source')
2051
self.build_tree(['source/file', 'source/FILE'])
2052
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2053
source.commit('added files')
2054
# Don't try this at home, kids!
2055
# Force the tree to report that it is case insensitive
2056
target = self.make_branch_and_tree('target')
2057
target.case_sensitive = False
2058
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2059
self.assertEqual('file.moved', target.id2path('lower-id'))
2060
self.assertEqual('FILE', target.id2path('upper-id'))
2063
class TestCommitTransform(tests.TestCaseWithTransport):
2065
def get_branch(self):
2066
tree = self.make_branch_and_tree('tree')
2068
self.addCleanup(tree.unlock)
2069
tree.commit('empty commit')
2072
def get_branch_and_transform(self):
2073
branch = self.get_branch()
2074
tt = TransformPreview(branch.basis_tree())
2075
self.addCleanup(tt.finalize)
2078
def test_commit_wrong_basis(self):
2079
branch = self.get_branch()
2080
basis = branch.repository.revision_tree(
2081
_mod_revision.NULL_REVISION)
2082
tt = TransformPreview(basis)
2083
self.addCleanup(tt.finalize)
2084
e = self.assertRaises(ValueError, tt.commit, branch, '')
2085
self.assertEqual('TreeTransform not based on branch basis: null:',
2088
def test_empy_commit(self):
2089
branch, tt = self.get_branch_and_transform()
2090
rev = tt.commit(branch, 'my message')
2091
self.assertEqual(2, branch.revno())
2092
repo = branch.repository
2093
self.assertEqual('my message', repo.get_revision(rev).message)
2095
def test_merge_parents(self):
2096
branch, tt = self.get_branch_and_transform()
2097
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2098
self.assertEqual(['rev1b', 'rev1c'],
2099
branch.basis_tree().get_parent_ids()[1:])
2101
def test_first_commit(self):
2102
branch = self.make_branch('branch')
2104
self.addCleanup(branch.unlock)
2105
tt = TransformPreview(branch.basis_tree())
2106
self.addCleanup(tt.finalize)
2107
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2108
rev = tt.commit(branch, 'my message')
2109
self.assertEqual([], branch.basis_tree().get_parent_ids())
2110
self.assertNotEqual(_mod_revision.NULL_REVISION,
2111
branch.last_revision())
2113
def test_first_commit_with_merge_parents(self):
2114
branch = self.make_branch('branch')
2116
self.addCleanup(branch.unlock)
2117
tt = TransformPreview(branch.basis_tree())
2118
self.addCleanup(tt.finalize)
2119
e = self.assertRaises(ValueError, tt.commit, branch,
2120
'my message', ['rev1b-id'])
2121
self.assertEqual('Cannot supply merge parents for first commit.',
2123
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2125
def test_add_files(self):
2126
branch, tt = self.get_branch_and_transform()
2127
tt.new_file('file', tt.root, 'contents', 'file-id')
2128
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2129
if SymlinkFeature.available():
2130
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2131
rev = tt.commit(branch, 'message')
2132
tree = branch.basis_tree()
2133
self.assertEqual('file', tree.id2path('file-id'))
2134
self.assertEqual('contents', tree.get_file_text('file-id'))
2135
self.assertEqual('dir', tree.id2path('dir-id'))
2136
if SymlinkFeature.available():
2137
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2138
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2140
def test_add_unversioned(self):
2141
branch, tt = self.get_branch_and_transform()
2142
tt.new_file('file', tt.root, 'contents')
2143
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2144
'message', strict=True)
2146
def test_modify_strict(self):
2147
branch, tt = self.get_branch_and_transform()
2148
tt.new_file('file', tt.root, 'contents', 'file-id')
2149
tt.commit(branch, 'message', strict=True)
2150
tt = TransformPreview(branch.basis_tree())
2151
self.addCleanup(tt.finalize)
2152
trans_id = tt.trans_id_file_id('file-id')
2153
tt.delete_contents(trans_id)
2154
tt.create_file('contents', trans_id)
2155
tt.commit(branch, 'message', strict=True)
2157
def test_commit_malformed(self):
2158
"""Committing a malformed transform should raise an exception.
2160
In this case, we are adding a file without adding its parent.
2162
branch, tt = self.get_branch_and_transform()
2163
parent_id = tt.trans_id_file_id('parent-id')
2164
tt.new_file('file', parent_id, 'contents', 'file-id')
2165
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2168
def test_commit_rich_revision_data(self):
2169
branch, tt = self.get_branch_and_transform()
2170
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2171
committer='me <me@example.com>',
2172
revprops={'foo': 'bar'}, revision_id='revid-1',
2173
authors=['Author1 <author1@example.com>',
2174
'Author2 <author2@example.com>',
2176
self.assertEqual('revid-1', rev_id)
2177
revision = branch.repository.get_revision(rev_id)
2178
self.assertEqual(1, revision.timestamp)
2179
self.assertEqual(43201, revision.timezone)
2180
self.assertEqual('me <me@example.com>', revision.committer)
2181
self.assertEqual(['Author1 <author1@example.com>',
2182
'Author2 <author2@example.com>'],
2183
revision.get_apparent_authors())
2184
del revision.properties['authors']
2185
self.assertEqual({'foo': 'bar',
2186
'branch-nick': 'tree'},
2187
revision.properties)
2189
def test_no_explicit_revprops(self):
2190
branch, tt = self.get_branch_and_transform()
2191
rev_id = tt.commit(branch, 'message', authors=[
2192
'Author1 <author1@example.com>',
2193
'Author2 <author2@example.com>', ])
2194
revision = branch.repository.get_revision(rev_id)
2195
self.assertEqual(['Author1 <author1@example.com>',
2196
'Author2 <author2@example.com>'],
2197
revision.get_apparent_authors())
2198
self.assertEqual('tree', revision.properties['branch-nick'])
2201
class TestBackupName(tests.TestCase):
2203
def test_deprecations(self):
2204
class MockTransform(object):
2206
def has_named_child(self, by_parent, parent_id, name):
2207
return name in by_parent.get(parent_id, [])
2209
class MockEntry(object):
2212
object.__init__(self)
733
class MockTransform(object):
735
def has_named_child(self, by_parent, parent_id, name):
736
for child_id in by_parent[parent_id]:
740
elif name == "name.~%s~" % child_id:
744
class MockEntry(object):
746
object.__init__(self)
749
class TestGetBackupName(TestCase):
750
def test_get_backup_name(self):
2215
751
tt = MockTransform()
2216
name1 = self.applyDeprecated(
2217
symbol_versioning.deprecated_in((2, 3, 0)),
2218
transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
2219
self.assertEqual('name.~1~', name1)
2220
name2 = self.applyDeprecated(
2221
symbol_versioning.deprecated_in((2, 3, 0)),
2222
transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
2223
self.assertEqual('name.~2~', name2)
2226
class TestFileMover(tests.TestCaseWithTransport):
2228
def test_file_mover(self):
2229
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2230
mover = _FileMover()
2231
mover.rename('a', 'q')
2232
self.failUnlessExists('q')
2233
self.failIfExists('a')
2234
self.failUnlessExists('q/b')
2235
self.failUnlessExists('c')
2236
self.failUnlessExists('c/d')
2238
def test_pre_delete_rollback(self):
2239
self.build_tree(['a/'])
2240
mover = _FileMover()
2241
mover.pre_delete('a', 'q')
2242
self.failUnlessExists('q')
2243
self.failIfExists('a')
2245
self.failIfExists('q')
2246
self.failUnlessExists('a')
2248
def test_apply_deletions(self):
2249
self.build_tree(['a/', 'b/'])
2250
mover = _FileMover()
2251
mover.pre_delete('a', 'q')
2252
mover.pre_delete('b', 'r')
2253
self.failUnlessExists('q')
2254
self.failUnlessExists('r')
2255
self.failIfExists('a')
2256
self.failIfExists('b')
2257
mover.apply_deletions()
2258
self.failIfExists('q')
2259
self.failIfExists('r')
2260
self.failIfExists('a')
2261
self.failIfExists('b')
2263
def test_file_mover_rollback(self):
2264
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2265
mover = _FileMover()
2266
mover.rename('c/d', 'c/f')
2267
mover.rename('c/e', 'c/d')
2269
mover.rename('a', 'c')
2270
except errors.FileExists, e:
2272
self.failUnlessExists('a')
2273
self.failUnlessExists('c/d')
2276
class Bogus(Exception):
2280
class TestTransformRollback(tests.TestCaseWithTransport):
2282
class ExceptionFileMover(_FileMover):
2284
def __init__(self, bad_source=None, bad_target=None):
2285
_FileMover.__init__(self)
2286
self.bad_source = bad_source
2287
self.bad_target = bad_target
2289
def rename(self, source, target):
2290
if (self.bad_source is not None and
2291
source.endswith(self.bad_source)):
2293
elif (self.bad_target is not None and
2294
target.endswith(self.bad_target)):
2297
_FileMover.rename(self, source, target)
2299
def test_rollback_rename(self):
2300
tree = self.make_branch_and_tree('.')
2301
self.build_tree(['a/', 'a/b'])
2302
tt = TreeTransform(tree)
2303
self.addCleanup(tt.finalize)
2304
a_id = tt.trans_id_tree_path('a')
2305
tt.adjust_path('c', tt.root, a_id)
2306
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2307
self.assertRaises(Bogus, tt.apply,
2308
_mover=self.ExceptionFileMover(bad_source='a'))
2309
self.failUnlessExists('a')
2310
self.failUnlessExists('a/b')
2312
self.failUnlessExists('c')
2313
self.failUnlessExists('c/d')
2315
def test_rollback_rename_into_place(self):
2316
tree = self.make_branch_and_tree('.')
2317
self.build_tree(['a/', 'a/b'])
2318
tt = TreeTransform(tree)
2319
self.addCleanup(tt.finalize)
2320
a_id = tt.trans_id_tree_path('a')
2321
tt.adjust_path('c', tt.root, a_id)
2322
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2323
self.assertRaises(Bogus, tt.apply,
2324
_mover=self.ExceptionFileMover(bad_target='c/d'))
2325
self.failUnlessExists('a')
2326
self.failUnlessExists('a/b')
2328
self.failUnlessExists('c')
2329
self.failUnlessExists('c/d')
2331
def test_rollback_deletion(self):
2332
tree = self.make_branch_and_tree('.')
2333
self.build_tree(['a/', 'a/b'])
2334
tt = TreeTransform(tree)
2335
self.addCleanup(tt.finalize)
2336
a_id = tt.trans_id_tree_path('a')
2337
tt.delete_contents(a_id)
2338
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2339
self.assertRaises(Bogus, tt.apply,
2340
_mover=self.ExceptionFileMover(bad_target='d'))
2341
self.failUnlessExists('a')
2342
self.failUnlessExists('a/b')
2345
class TestTransformMissingParent(tests.TestCaseWithTransport):
2347
def make_tt_with_versioned_dir(self):
2348
wt = self.make_branch_and_tree('.')
2349
self.build_tree(['dir/',])
2350
wt.add(['dir'], ['dir-id'])
2351
wt.commit('Create dir')
2352
tt = TreeTransform(wt)
2353
self.addCleanup(tt.finalize)
2356
def test_resolve_create_parent_for_versioned_file(self):
2357
wt, tt = self.make_tt_with_versioned_dir()
2358
dir_tid = tt.trans_id_tree_file_id('dir-id')
2359
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2360
tt.delete_contents(dir_tid)
2361
tt.unversion_file(dir_tid)
2362
conflicts = resolve_conflicts(tt)
2363
# one conflict for the missing directory, one for the unversioned
2365
self.assertLength(2, conflicts)
2367
def test_non_versioned_file_create_conflict(self):
2368
wt, tt = self.make_tt_with_versioned_dir()
2369
dir_tid = tt.trans_id_tree_file_id('dir-id')
2370
tt.new_file('file', dir_tid, 'Contents')
2371
tt.delete_contents(dir_tid)
2372
tt.unversion_file(dir_tid)
2373
conflicts = resolve_conflicts(tt)
2374
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2375
self.assertLength(1, conflicts)
2376
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2380
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2381
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2383
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2384
('', ''), ('directory', 'directory'), (False, None))
2387
class TestTransformPreview(tests.TestCaseWithTransport):
2389
def create_tree(self):
2390
tree = self.make_branch_and_tree('.')
2391
self.build_tree_contents([('a', 'content 1')])
2392
tree.set_root_id('TREE_ROOT')
2393
tree.add('a', 'a-id')
2394
tree.commit('rev1', rev_id='rev1')
2395
return tree.branch.repository.revision_tree('rev1')
2397
def get_empty_preview(self):
2398
repository = self.make_repository('repo')
2399
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2400
preview = TransformPreview(tree)
2401
self.addCleanup(preview.finalize)
2404
def test_transform_preview(self):
2405
revision_tree = self.create_tree()
2406
preview = TransformPreview(revision_tree)
2407
self.addCleanup(preview.finalize)
2409
def test_transform_preview_tree(self):
2410
revision_tree = self.create_tree()
2411
preview = TransformPreview(revision_tree)
2412
self.addCleanup(preview.finalize)
2413
preview.get_preview_tree()
2415
def test_transform_new_file(self):
2416
revision_tree = self.create_tree()
2417
preview = TransformPreview(revision_tree)
2418
self.addCleanup(preview.finalize)
2419
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2420
preview_tree = preview.get_preview_tree()
2421
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2423
preview_tree.get_file('file2-id').read(), 'content B\n')
2425
def test_diff_preview_tree(self):
2426
revision_tree = self.create_tree()
2427
preview = TransformPreview(revision_tree)
2428
self.addCleanup(preview.finalize)
2429
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2430
preview_tree = preview.get_preview_tree()
2432
show_diff_trees(revision_tree, preview_tree, out)
2433
lines = out.getvalue().splitlines()
2434
self.assertEqual(lines[0], "=== added file 'file2'")
2435
# 3 lines of diff administrivia
2436
self.assertEqual(lines[4], "+content B")
2438
def test_transform_conflicts(self):
2439
revision_tree = self.create_tree()
2440
preview = TransformPreview(revision_tree)
2441
self.addCleanup(preview.finalize)
2442
preview.new_file('a', preview.root, 'content 2')
2443
resolve_conflicts(preview)
2444
trans_id = preview.trans_id_file_id('a-id')
2445
self.assertEqual('a.moved', preview.final_name(trans_id))
2447
def get_tree_and_preview_tree(self):
2448
revision_tree = self.create_tree()
2449
preview = TransformPreview(revision_tree)
2450
self.addCleanup(preview.finalize)
2451
a_trans_id = preview.trans_id_file_id('a-id')
2452
preview.delete_contents(a_trans_id)
2453
preview.create_file('b content', a_trans_id)
2454
preview_tree = preview.get_preview_tree()
2455
return revision_tree, preview_tree
2457
def test_iter_changes(self):
2458
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2459
root = revision_tree.inventory.root.file_id
2460
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2461
(root, root), ('a', 'a'), ('file', 'file'),
2463
list(preview_tree.iter_changes(revision_tree)))
2465
def test_include_unchanged_succeeds(self):
2466
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2467
changes = preview_tree.iter_changes(revision_tree,
2468
include_unchanged=True)
2469
root = revision_tree.inventory.root.file_id
2471
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2473
def test_specific_files(self):
2474
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2475
changes = preview_tree.iter_changes(revision_tree,
2476
specific_files=[''])
2477
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2479
def test_want_unversioned(self):
2480
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2481
changes = preview_tree.iter_changes(revision_tree,
2482
want_unversioned=True)
2483
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2485
def test_ignore_extra_trees_no_specific_files(self):
2486
# extra_trees is harmless without specific_files, so we'll silently
2487
# accept it, even though we won't use it.
2488
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2489
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2491
def test_ignore_require_versioned_no_specific_files(self):
2492
# require_versioned is meaningless without specific_files.
2493
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2494
preview_tree.iter_changes(revision_tree, require_versioned=False)
2496
def test_ignore_pb(self):
2497
# pb could be supported, but TT.iter_changes doesn't support it.
2498
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2499
preview_tree.iter_changes(revision_tree)
2501
def test_kind(self):
2502
revision_tree = self.create_tree()
2503
preview = TransformPreview(revision_tree)
2504
self.addCleanup(preview.finalize)
2505
preview.new_file('file', preview.root, 'contents', 'file-id')
2506
preview.new_directory('directory', preview.root, 'dir-id')
2507
preview_tree = preview.get_preview_tree()
2508
self.assertEqual('file', preview_tree.kind('file-id'))
2509
self.assertEqual('directory', preview_tree.kind('dir-id'))
2511
def test_get_file_mtime(self):
2512
preview = self.get_empty_preview()
2513
file_trans_id = preview.new_file('file', preview.root, 'contents',
2515
limbo_path = preview._limbo_name(file_trans_id)
2516
preview_tree = preview.get_preview_tree()
2517
self.assertEqual(os.stat(limbo_path).st_mtime,
2518
preview_tree.get_file_mtime('file-id'))
2520
def test_get_file_mtime_renamed(self):
2521
work_tree = self.make_branch_and_tree('tree')
2522
self.build_tree(['tree/file'])
2523
work_tree.add('file', 'file-id')
2524
preview = TransformPreview(work_tree)
2525
self.addCleanup(preview.finalize)
2526
file_trans_id = preview.trans_id_tree_file_id('file-id')
2527
preview.adjust_path('renamed', preview.root, file_trans_id)
2528
preview_tree = preview.get_preview_tree()
2529
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2530
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2532
def test_get_file(self):
2533
preview = self.get_empty_preview()
2534
preview.new_file('file', preview.root, 'contents', 'file-id')
2535
preview_tree = preview.get_preview_tree()
2536
tree_file = preview_tree.get_file('file-id')
2538
self.assertEqual('contents', tree_file.read())
2542
def test_get_symlink_target(self):
2543
self.requireFeature(SymlinkFeature)
2544
preview = self.get_empty_preview()
2545
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2546
preview_tree = preview.get_preview_tree()
2547
self.assertEqual('target',
2548
preview_tree.get_symlink_target('symlink-id'))
2550
def test_all_file_ids(self):
2551
tree = self.make_branch_and_tree('tree')
2552
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2553
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2554
preview = TransformPreview(tree)
2555
self.addCleanup(preview.finalize)
2556
preview.unversion_file(preview.trans_id_file_id('b-id'))
2557
c_trans_id = preview.trans_id_file_id('c-id')
2558
preview.unversion_file(c_trans_id)
2559
preview.version_file('c-id', c_trans_id)
2560
preview_tree = preview.get_preview_tree()
2561
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2562
preview_tree.all_file_ids())
2564
def test_path2id_deleted_unchanged(self):
2565
tree = self.make_branch_and_tree('tree')
2566
self.build_tree(['tree/unchanged', 'tree/deleted'])
2567
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2568
preview = TransformPreview(tree)
2569
self.addCleanup(preview.finalize)
2570
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2571
preview_tree = preview.get_preview_tree()
2572
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2573
self.assertIs(None, preview_tree.path2id('deleted'))
2575
def test_path2id_created(self):
2576
tree = self.make_branch_and_tree('tree')
2577
self.build_tree(['tree/unchanged'])
2578
tree.add(['unchanged'], ['unchanged-id'])
2579
preview = TransformPreview(tree)
2580
self.addCleanup(preview.finalize)
2581
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2582
'contents', 'new-id')
2583
preview_tree = preview.get_preview_tree()
2584
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2586
def test_path2id_moved(self):
2587
tree = self.make_branch_and_tree('tree')
2588
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2589
tree.add(['old_parent', 'old_parent/child'],
2590
['old_parent-id', 'child-id'])
2591
preview = TransformPreview(tree)
2592
self.addCleanup(preview.finalize)
2593
new_parent = preview.new_directory('new_parent', preview.root,
2595
preview.adjust_path('child', new_parent,
2596
preview.trans_id_file_id('child-id'))
2597
preview_tree = preview.get_preview_tree()
2598
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2599
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2601
def test_path2id_renamed_parent(self):
2602
tree = self.make_branch_and_tree('tree')
2603
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2604
tree.add(['old_name', 'old_name/child'],
2605
['parent-id', 'child-id'])
2606
preview = TransformPreview(tree)
2607
self.addCleanup(preview.finalize)
2608
preview.adjust_path('new_name', preview.root,
2609
preview.trans_id_file_id('parent-id'))
2610
preview_tree = preview.get_preview_tree()
2611
self.assertIs(None, preview_tree.path2id('old_name/child'))
2612
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2614
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2615
preview_tree = tt.get_preview_tree()
2616
preview_result = list(preview_tree.iter_entries_by_dir(
2620
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2621
self.assertEqual(actual_result, preview_result)
2623
def test_iter_entries_by_dir_new(self):
2624
tree = self.make_branch_and_tree('tree')
2625
tt = TreeTransform(tree)
2626
tt.new_file('new', tt.root, 'contents', 'new-id')
2627
self.assertMatchingIterEntries(tt)
2629
def test_iter_entries_by_dir_deleted(self):
2630
tree = self.make_branch_and_tree('tree')
2631
self.build_tree(['tree/deleted'])
2632
tree.add('deleted', 'deleted-id')
2633
tt = TreeTransform(tree)
2634
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2635
self.assertMatchingIterEntries(tt)
2637
def test_iter_entries_by_dir_unversioned(self):
2638
tree = self.make_branch_and_tree('tree')
2639
self.build_tree(['tree/removed'])
2640
tree.add('removed', 'removed-id')
2641
tt = TreeTransform(tree)
2642
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2643
self.assertMatchingIterEntries(tt)
2645
def test_iter_entries_by_dir_moved(self):
2646
tree = self.make_branch_and_tree('tree')
2647
self.build_tree(['tree/moved', 'tree/new_parent/'])
2648
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2649
tt = TreeTransform(tree)
2650
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2651
tt.trans_id_file_id('moved-id'))
2652
self.assertMatchingIterEntries(tt)
2654
def test_iter_entries_by_dir_specific_file_ids(self):
2655
tree = self.make_branch_and_tree('tree')
2656
tree.set_root_id('tree-root-id')
2657
self.build_tree(['tree/parent/', 'tree/parent/child'])
2658
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2659
tt = TreeTransform(tree)
2660
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2662
def test_symlink_content_summary(self):
2663
self.requireFeature(SymlinkFeature)
2664
preview = self.get_empty_preview()
2665
preview.new_symlink('path', preview.root, 'target', 'path-id')
2666
summary = preview.get_preview_tree().path_content_summary('path')
2667
self.assertEqual(('symlink', None, None, 'target'), summary)
2669
def test_missing_content_summary(self):
2670
preview = self.get_empty_preview()
2671
summary = preview.get_preview_tree().path_content_summary('path')
2672
self.assertEqual(('missing', None, None, None), summary)
2674
def test_deleted_content_summary(self):
2675
tree = self.make_branch_and_tree('tree')
2676
self.build_tree(['tree/path/'])
2678
preview = TransformPreview(tree)
2679
self.addCleanup(preview.finalize)
2680
preview.delete_contents(preview.trans_id_tree_path('path'))
2681
summary = preview.get_preview_tree().path_content_summary('path')
2682
self.assertEqual(('missing', None, None, None), summary)
2684
def test_file_content_summary_executable(self):
2685
preview = self.get_empty_preview()
2686
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2687
preview.set_executability(True, path_id)
2688
summary = preview.get_preview_tree().path_content_summary('path')
2689
self.assertEqual(4, len(summary))
2690
self.assertEqual('file', summary[0])
2691
# size must be known
2692
self.assertEqual(len('contents'), summary[1])
2694
self.assertEqual(True, summary[2])
2695
# will not have hash (not cheap to determine)
2696
self.assertIs(None, summary[3])
2698
def test_change_executability(self):
2699
tree = self.make_branch_and_tree('tree')
2700
self.build_tree(['tree/path'])
2702
preview = TransformPreview(tree)
2703
self.addCleanup(preview.finalize)
2704
path_id = preview.trans_id_tree_path('path')
2705
preview.set_executability(True, path_id)
2706
summary = preview.get_preview_tree().path_content_summary('path')
2707
self.assertEqual(True, summary[2])
2709
def test_file_content_summary_non_exec(self):
2710
preview = self.get_empty_preview()
2711
preview.new_file('path', preview.root, 'contents', 'path-id')
2712
summary = preview.get_preview_tree().path_content_summary('path')
2713
self.assertEqual(4, len(summary))
2714
self.assertEqual('file', summary[0])
2715
# size must be known
2716
self.assertEqual(len('contents'), summary[1])
2718
self.assertEqual(False, summary[2])
2719
# will not have hash (not cheap to determine)
2720
self.assertIs(None, summary[3])
2722
def test_dir_content_summary(self):
2723
preview = self.get_empty_preview()
2724
preview.new_directory('path', preview.root, 'path-id')
2725
summary = preview.get_preview_tree().path_content_summary('path')
2726
self.assertEqual(('directory', None, None, None), summary)
2728
def test_tree_content_summary(self):
2729
preview = self.get_empty_preview()
2730
path = preview.new_directory('path', preview.root, 'path-id')
2731
preview.set_tree_reference('rev-1', path)
2732
summary = preview.get_preview_tree().path_content_summary('path')
2733
self.assertEqual(4, len(summary))
2734
self.assertEqual('tree-reference', summary[0])
2736
def test_annotate(self):
2737
tree = self.make_branch_and_tree('tree')
2738
self.build_tree_contents([('tree/file', 'a\n')])
2739
tree.add('file', 'file-id')
2740
tree.commit('a', rev_id='one')
2741
self.build_tree_contents([('tree/file', 'a\nb\n')])
2742
preview = TransformPreview(tree)
2743
self.addCleanup(preview.finalize)
2744
file_trans_id = preview.trans_id_file_id('file-id')
2745
preview.delete_contents(file_trans_id)
2746
preview.create_file('a\nb\nc\n', file_trans_id)
2747
preview_tree = preview.get_preview_tree()
2753
annotation = preview_tree.annotate_iter('file-id', 'me:')
2754
self.assertEqual(expected, annotation)
2756
def test_annotate_missing(self):
2757
preview = self.get_empty_preview()
2758
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2759
preview_tree = preview.get_preview_tree()
2765
annotation = preview_tree.annotate_iter('file-id', 'me:')
2766
self.assertEqual(expected, annotation)
2768
def test_annotate_rename(self):
2769
tree = self.make_branch_and_tree('tree')
2770
self.build_tree_contents([('tree/file', 'a\n')])
2771
tree.add('file', 'file-id')
2772
tree.commit('a', rev_id='one')
2773
preview = TransformPreview(tree)
2774
self.addCleanup(preview.finalize)
2775
file_trans_id = preview.trans_id_file_id('file-id')
2776
preview.adjust_path('newname', preview.root, file_trans_id)
2777
preview_tree = preview.get_preview_tree()
2781
annotation = preview_tree.annotate_iter('file-id', 'me:')
2782
self.assertEqual(expected, annotation)
2784
def test_annotate_deleted(self):
2785
tree = self.make_branch_and_tree('tree')
2786
self.build_tree_contents([('tree/file', 'a\n')])
2787
tree.add('file', 'file-id')
2788
tree.commit('a', rev_id='one')
2789
self.build_tree_contents([('tree/file', 'a\nb\n')])
2790
preview = TransformPreview(tree)
2791
self.addCleanup(preview.finalize)
2792
file_trans_id = preview.trans_id_file_id('file-id')
2793
preview.delete_contents(file_trans_id)
2794
preview_tree = preview.get_preview_tree()
2795
annotation = preview_tree.annotate_iter('file-id', 'me:')
2796
self.assertIs(None, annotation)
2798
def test_stored_kind(self):
2799
preview = self.get_empty_preview()
2800
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2801
preview_tree = preview.get_preview_tree()
2802
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2804
def test_is_executable(self):
2805
preview = self.get_empty_preview()
2806
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2807
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2808
preview_tree = preview.get_preview_tree()
2809
self.assertEqual(True, preview_tree.is_executable('file-id'))
2811
def test_get_set_parent_ids(self):
2812
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2813
self.assertEqual([], preview_tree.get_parent_ids())
2814
preview_tree.set_parent_ids(['rev-1'])
2815
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2817
def test_plan_file_merge(self):
2818
work_a = self.make_branch_and_tree('wta')
2819
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2820
work_a.add('file', 'file-id')
2821
base_id = work_a.commit('base version')
2822
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2823
preview = TransformPreview(work_a)
2824
self.addCleanup(preview.finalize)
2825
trans_id = preview.trans_id_file_id('file-id')
2826
preview.delete_contents(trans_id)
2827
preview.create_file('b\nc\nd\ne\n', trans_id)
2828
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2829
tree_a = preview.get_preview_tree()
2830
tree_a.set_parent_ids([base_id])
2832
('killed-a', 'a\n'),
2833
('killed-b', 'b\n'),
2834
('unchanged', 'c\n'),
2835
('unchanged', 'd\n'),
2838
], list(tree_a.plan_file_merge('file-id', tree_b)))
2840
def test_plan_file_merge_revision_tree(self):
2841
work_a = self.make_branch_and_tree('wta')
2842
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2843
work_a.add('file', 'file-id')
2844
base_id = work_a.commit('base version')
2845
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2846
preview = TransformPreview(work_a.basis_tree())
2847
self.addCleanup(preview.finalize)
2848
trans_id = preview.trans_id_file_id('file-id')
2849
preview.delete_contents(trans_id)
2850
preview.create_file('b\nc\nd\ne\n', trans_id)
2851
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2852
tree_a = preview.get_preview_tree()
2853
tree_a.set_parent_ids([base_id])
2855
('killed-a', 'a\n'),
2856
('killed-b', 'b\n'),
2857
('unchanged', 'c\n'),
2858
('unchanged', 'd\n'),
2861
], list(tree_a.plan_file_merge('file-id', tree_b)))
2863
def test_walkdirs(self):
2864
preview = self.get_empty_preview()
2865
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2866
# FIXME: new_directory should mark root.
2867
preview.fixup_new_roots()
2868
preview_tree = preview.get_preview_tree()
2869
file_trans_id = preview.new_file('a', preview.root, 'contents',
2871
expected = [(('', 'tree-root'),
2872
[('a', 'a', 'file', None, 'a-id', 'file')])]
2873
self.assertEqual(expected, list(preview_tree.walkdirs()))
2875
def test_extras(self):
2876
work_tree = self.make_branch_and_tree('tree')
2877
self.build_tree(['tree/removed-file', 'tree/existing-file',
2878
'tree/not-removed-file'])
2879
work_tree.add(['removed-file', 'not-removed-file'])
2880
preview = TransformPreview(work_tree)
2881
self.addCleanup(preview.finalize)
2882
preview.new_file('new-file', preview.root, 'contents')
2883
preview.new_file('new-versioned-file', preview.root, 'contents',
2885
tree = preview.get_preview_tree()
2886
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2887
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2890
def test_merge_into_preview(self):
2891
work_tree = self.make_branch_and_tree('tree')
2892
self.build_tree_contents([('tree/file','b\n')])
2893
work_tree.add('file', 'file-id')
2894
work_tree.commit('first commit')
2895
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2896
self.build_tree_contents([('child/file','b\nc\n')])
2897
child_tree.commit('child commit')
2898
child_tree.lock_write()
2899
self.addCleanup(child_tree.unlock)
2900
work_tree.lock_write()
2901
self.addCleanup(work_tree.unlock)
2902
preview = TransformPreview(work_tree)
2903
self.addCleanup(preview.finalize)
2904
file_trans_id = preview.trans_id_file_id('file-id')
2905
preview.delete_contents(file_trans_id)
2906
preview.create_file('a\nb\n', file_trans_id)
2907
preview_tree = preview.get_preview_tree()
2908
merger = Merger.from_revision_ids(None, preview_tree,
2909
child_tree.branch.last_revision(),
2910
other_branch=child_tree.branch,
2911
tree_branch=work_tree.branch)
2912
merger.merge_type = Merge3Merger
2913
tt = merger.make_merger().make_preview_transform()
2914
self.addCleanup(tt.finalize)
2915
final_tree = tt.get_preview_tree()
2916
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2918
def test_merge_preview_into_workingtree(self):
2919
tree = self.make_branch_and_tree('tree')
2920
tree.set_root_id('TREE_ROOT')
2921
tt = TransformPreview(tree)
2922
self.addCleanup(tt.finalize)
2923
tt.new_file('name', tt.root, 'content', 'file-id')
2924
tree2 = self.make_branch_and_tree('tree2')
2925
tree2.set_root_id('TREE_ROOT')
2926
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2927
None, tree.basis_tree())
2928
merger.merge_type = Merge3Merger
2931
def test_merge_preview_into_workingtree_handles_conflicts(self):
2932
tree = self.make_branch_and_tree('tree')
2933
self.build_tree_contents([('tree/foo', 'bar')])
2934
tree.add('foo', 'foo-id')
2936
tt = TransformPreview(tree)
2937
self.addCleanup(tt.finalize)
2938
trans_id = tt.trans_id_file_id('foo-id')
2939
tt.delete_contents(trans_id)
2940
tt.create_file('baz', trans_id)
2941
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2942
self.build_tree_contents([('tree2/foo', 'qux')])
2944
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2945
pb, tree.basis_tree())
2946
merger.merge_type = Merge3Merger
2949
def test_is_executable(self):
2950
tree = self.make_branch_and_tree('tree')
2951
preview = TransformPreview(tree)
2952
self.addCleanup(preview.finalize)
2953
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2954
preview_tree = preview.get_preview_tree()
2955
self.assertEqual(False, preview_tree.is_executable('baz-id',
2957
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2959
def test_commit_preview_tree(self):
2960
tree = self.make_branch_and_tree('tree')
2961
rev_id = tree.commit('rev1')
2962
tree.branch.lock_write()
2963
self.addCleanup(tree.branch.unlock)
2964
tt = TransformPreview(tree)
2965
tt.new_file('file', tt.root, 'contents', 'file_id')
2966
self.addCleanup(tt.finalize)
2967
preview = tt.get_preview_tree()
2968
preview.set_parent_ids([rev_id])
2969
builder = tree.branch.get_commit_builder([rev_id])
2970
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2971
builder.finish_inventory()
2972
rev2_id = builder.commit('rev2')
2973
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2974
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2976
def test_ascii_limbo_paths(self):
2977
self.requireFeature(tests.UnicodeFilenameFeature)
2978
branch = self.make_branch('any')
2979
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2980
tt = TransformPreview(tree)
2981
self.addCleanup(tt.finalize)
2982
foo_id = tt.new_directory('', ROOT_PARENT)
2983
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
2984
limbo_path = tt._limbo_name(bar_id)
2985
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
2988
class FakeSerializer(object):
2989
"""Serializer implementation that simply returns the input.
2991
The input is returned in the order used by pack.ContainerPushParser.
2994
def bytes_record(bytes, names):
2998
class TestSerializeTransform(tests.TestCaseWithTransport):
3000
_test_needs_features = [tests.UnicodeFilenameFeature]
3002
def get_preview(self, tree=None):
3004
tree = self.make_branch_and_tree('tree')
3005
tt = TransformPreview(tree)
3006
self.addCleanup(tt.finalize)
3009
def assertSerializesTo(self, expected, tt):
3010
records = list(tt.serialize(FakeSerializer()))
3011
self.assertEqual(expected, records)
3014
def default_attribs():
3019
'_new_executability': {},
3021
'_tree_path_ids': {'': 'new-0'},
3023
'_removed_contents': [],
3024
'_non_present_ids': {},
3027
def make_records(self, attribs, contents):
3029
(((('attribs'),),), bencode.bencode(attribs))]
3030
records.extend([(((n, k),), c) for n, k, c in contents])
3033
def creation_records(self):
3034
attribs = self.default_attribs()
3035
attribs['_id_number'] = 3
3036
attribs['_new_name'] = {
3037
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3038
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3039
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3040
attribs['_new_executability'] = {'new-1': 1}
3042
('new-1', 'file', 'i 1\nbar\n'),
3043
('new-2', 'directory', ''),
3045
return self.make_records(attribs, contents)
3047
def test_serialize_creation(self):
3048
tt = self.get_preview()
3049
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3050
tt.new_directory('qux', tt.root, 'quxx')
3051
self.assertSerializesTo(self.creation_records(), tt)
3053
def test_deserialize_creation(self):
3054
tt = self.get_preview()
3055
tt.deserialize(iter(self.creation_records()))
3056
self.assertEqual(3, tt._id_number)
3057
self.assertEqual({'new-1': u'foo\u1234',
3058
'new-2': 'qux'}, tt._new_name)
3059
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3060
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3061
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3062
self.assertEqual({'new-1': True}, tt._new_executability)
3063
self.assertEqual({'new-1': 'file',
3064
'new-2': 'directory'}, tt._new_contents)
3065
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3067
foo_content = foo_limbo.read()
3070
self.assertEqual('bar', foo_content)
3072
def symlink_creation_records(self):
3073
attribs = self.default_attribs()
3074
attribs['_id_number'] = 2
3075
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3076
attribs['_new_parent'] = {'new-1': 'new-0'}
3077
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3078
return self.make_records(attribs, contents)
3080
def test_serialize_symlink_creation(self):
3081
self.requireFeature(tests.SymlinkFeature)
3082
tt = self.get_preview()
3083
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3084
self.assertSerializesTo(self.symlink_creation_records(), tt)
3086
def test_deserialize_symlink_creation(self):
3087
self.requireFeature(tests.SymlinkFeature)
3088
tt = self.get_preview()
3089
tt.deserialize(iter(self.symlink_creation_records()))
3090
abspath = tt._limbo_name('new-1')
3091
foo_content = osutils.readlink(abspath)
3092
self.assertEqual(u'bar\u1234', foo_content)
3094
def make_destruction_preview(self):
3095
tree = self.make_branch_and_tree('.')
3096
self.build_tree([u'foo\u1234', 'bar'])
3097
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3098
return self.get_preview(tree)
3100
def destruction_records(self):
3101
attribs = self.default_attribs()
3102
attribs['_id_number'] = 3
3103
attribs['_removed_id'] = ['new-1']
3104
attribs['_removed_contents'] = ['new-2']
3105
attribs['_tree_path_ids'] = {
3107
u'foo\u1234'.encode('utf-8'): 'new-1',
3110
return self.make_records(attribs, [])
3112
def test_serialize_destruction(self):
3113
tt = self.make_destruction_preview()
3114
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3115
tt.unversion_file(foo_trans_id)
3116
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3117
tt.delete_contents(bar_trans_id)
3118
self.assertSerializesTo(self.destruction_records(), tt)
3120
def test_deserialize_destruction(self):
3121
tt = self.make_destruction_preview()
3122
tt.deserialize(iter(self.destruction_records()))
3123
self.assertEqual({u'foo\u1234': 'new-1',
3125
'': tt.root}, tt._tree_path_ids)
3126
self.assertEqual({'new-1': u'foo\u1234',
3128
tt.root: ''}, tt._tree_id_paths)
3129
self.assertEqual(set(['new-1']), tt._removed_id)
3130
self.assertEqual(set(['new-2']), tt._removed_contents)
3132
def missing_records(self):
3133
attribs = self.default_attribs()
3134
attribs['_id_number'] = 2
3135
attribs['_non_present_ids'] = {
3137
return self.make_records(attribs, [])
3139
def test_serialize_missing(self):
3140
tt = self.get_preview()
3141
boo_trans_id = tt.trans_id_file_id('boo')
3142
self.assertSerializesTo(self.missing_records(), tt)
3144
def test_deserialize_missing(self):
3145
tt = self.get_preview()
3146
tt.deserialize(iter(self.missing_records()))
3147
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3149
def make_modification_preview(self):
3150
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3151
LINES_TWO = 'z\nbb\nx\ndd\n'
3152
tree = self.make_branch_and_tree('tree')
3153
self.build_tree_contents([('tree/file', LINES_ONE)])
3154
tree.add('file', 'file-id')
3155
return self.get_preview(tree), LINES_TWO
3157
def modification_records(self):
3158
attribs = self.default_attribs()
3159
attribs['_id_number'] = 2
3160
attribs['_tree_path_ids'] = {
3163
attribs['_removed_contents'] = ['new-1']
3164
contents = [('new-1', 'file',
3165
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3166
return self.make_records(attribs, contents)
3168
def test_serialize_modification(self):
3169
tt, LINES = self.make_modification_preview()
3170
trans_id = tt.trans_id_file_id('file-id')
3171
tt.delete_contents(trans_id)
3172
tt.create_file(LINES, trans_id)
3173
self.assertSerializesTo(self.modification_records(), tt)
3175
def test_deserialize_modification(self):
3176
tt, LINES = self.make_modification_preview()
3177
tt.deserialize(iter(self.modification_records()))
3178
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3180
def make_kind_change_preview(self):
3181
LINES = 'a\nb\nc\nd\n'
3182
tree = self.make_branch_and_tree('tree')
3183
self.build_tree(['tree/foo/'])
3184
tree.add('foo', 'foo-id')
3185
return self.get_preview(tree), LINES
3187
def kind_change_records(self):
3188
attribs = self.default_attribs()
3189
attribs['_id_number'] = 2
3190
attribs['_tree_path_ids'] = {
3193
attribs['_removed_contents'] = ['new-1']
3194
contents = [('new-1', 'file',
3195
'i 4\na\nb\nc\nd\n\n')]
3196
return self.make_records(attribs, contents)
3198
def test_serialize_kind_change(self):
3199
tt, LINES = self.make_kind_change_preview()
3200
trans_id = tt.trans_id_file_id('foo-id')
3201
tt.delete_contents(trans_id)
3202
tt.create_file(LINES, trans_id)
3203
self.assertSerializesTo(self.kind_change_records(), tt)
3205
def test_deserialize_kind_change(self):
3206
tt, LINES = self.make_kind_change_preview()
3207
tt.deserialize(iter(self.kind_change_records()))
3208
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3210
def make_add_contents_preview(self):
3211
LINES = 'a\nb\nc\nd\n'
3212
tree = self.make_branch_and_tree('tree')
3213
self.build_tree(['tree/foo'])
3215
os.unlink('tree/foo')
3216
return self.get_preview(tree), LINES
3218
def add_contents_records(self):
3219
attribs = self.default_attribs()
3220
attribs['_id_number'] = 2
3221
attribs['_tree_path_ids'] = {
3224
contents = [('new-1', 'file',
3225
'i 4\na\nb\nc\nd\n\n')]
3226
return self.make_records(attribs, contents)
3228
def test_serialize_add_contents(self):
3229
tt, LINES = self.make_add_contents_preview()
3230
trans_id = tt.trans_id_tree_path('foo')
3231
tt.create_file(LINES, trans_id)
3232
self.assertSerializesTo(self.add_contents_records(), tt)
3234
def test_deserialize_add_contents(self):
3235
tt, LINES = self.make_add_contents_preview()
3236
tt.deserialize(iter(self.add_contents_records()))
3237
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3239
def test_get_parents_lines(self):
3240
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3241
LINES_TWO = 'z\nbb\nx\ndd\n'
3242
tree = self.make_branch_and_tree('tree')
3243
self.build_tree_contents([('tree/file', LINES_ONE)])
3244
tree.add('file', 'file-id')
3245
tt = self.get_preview(tree)
3246
trans_id = tt.trans_id_tree_path('file')
3247
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3248
tt._get_parents_lines(trans_id))
3250
def test_get_parents_texts(self):
3251
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3252
LINES_TWO = 'z\nbb\nx\ndd\n'
3253
tree = self.make_branch_and_tree('tree')
3254
self.build_tree_contents([('tree/file', LINES_ONE)])
3255
tree.add('file', 'file-id')
3256
tt = self.get_preview(tree)
3257
trans_id = tt.trans_id_tree_path('file')
3258
self.assertEqual((LINES_ONE,),
3259
tt._get_parents_texts(trans_id))
3262
class TestOrphan(tests.TestCaseWithTransport):
3264
def test_no_orphan_for_transform_preview(self):
3265
tree = self.make_branch_and_tree('tree')
3266
tt = transform.TransformPreview(tree)
3267
self.addCleanup(tt.finalize)
3268
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3270
def _set_orphan_policy(self, wt, policy):
3271
wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
3274
def _prepare_orphan(self, wt):
3275
self.build_tree(['dir/', 'dir/foo'])
3276
wt.add(['dir'], ['dir-id'])
3277
wt.commit('add dir')
3278
tt = transform.TreeTransform(wt)
3279
self.addCleanup(tt.finalize)
3280
dir_tid = tt.trans_id_tree_path('dir')
3281
orphan_tid = tt.trans_id_tree_path('dir/foo')
3282
tt.delete_contents(dir_tid)
3283
tt.unversion_file(dir_tid)
3284
raw_conflicts = tt.find_conflicts()
3285
self.assertLength(1, raw_conflicts)
3286
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3287
return tt, orphan_tid
3289
def test_new_orphan_created(self):
3290
wt = self.make_branch_and_tree('.')
3291
self._set_orphan_policy(wt, 'move')
3292
tt, orphan_tid = self._prepare_orphan(wt)
3293
remaining_conflicts = resolve_conflicts(tt)
3294
# Yeah for resolved conflicts !
3295
self.assertLength(0, remaining_conflicts)
3296
# We have a new orphan
3297
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3298
self.assertEquals('bzr-orphans',
3299
tt.final_name(tt.final_parent(orphan_tid)))
3301
def test_never_orphan(self):
3302
wt = self.make_branch_and_tree('.')
3303
self._set_orphan_policy(wt, 'conflict')
3304
tt, orphan_tid = self._prepare_orphan(wt)
3305
remaining_conflicts = resolve_conflicts(tt)
3306
self.assertLength(1, remaining_conflicts)
3307
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3308
remaining_conflicts.pop())
3310
def test_orphan_error(self):
3311
def bogus_orphan(tt, orphan_id, parent_id):
3312
raise transform.OrphaningError(tt.final_name(orphan_id),
3313
tt.final_name(parent_id))
3314
transform.orphaning_registry.register('bogus', bogus_orphan,
3315
'Raise an error when orphaning')
3316
wt = self.make_branch_and_tree('.')
3317
self._set_orphan_policy(wt, 'bogus')
3318
tt, orphan_tid = self._prepare_orphan(wt)
3319
remaining_conflicts = resolve_conflicts(tt)
3320
self.assertLength(1, remaining_conflicts)
3321
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3322
remaining_conflicts.pop())
3324
def test_unknown_orphan_policy(self):
3325
wt = self.make_branch_and_tree('.')
3326
# Set a fictional policy nobody ever implemented
3327
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3328
tt, orphan_tid = self._prepare_orphan(wt)
3331
warnings.append(args[0] % args[1:])
3332
self.overrideAttr(trace, 'warning', warning)
3333
remaining_conflicts = resolve_conflicts(tt)
3334
# We fallback to the default policy which create a conflict
3335
self.assertLength(1, remaining_conflicts)
3336
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3337
remaining_conflicts.pop())
3338
self.assertLength(1, warnings)
3339
self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')
752
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
753
self.assertEqual(name, 'name.~1~')
754
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
755
self.assertEqual(name, 'name.~2~')
756
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
757
self.assertEqual(name, 'name.~1~')
758
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
759
self.assertEqual(name, 'name.~1~')
760
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
761
self.assertEqual(name, 'name.~4~')