479
972
rename.set_executability(True, myfile)
482
def test_find_interesting(self):
483
create, root = self.get_transform()
485
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
486
create.new_file('uvfile', root, 'othertext')
975
def test_rename_fails(self):
976
self.requireFeature(features.not_running_as_root)
977
# see https://bugs.launchpad.net/bzr/+bug/491763
978
create, root_id = self.get_transform()
979
first_dir = create.new_directory('first-dir', root_id, 'first-id')
980
myfile = create.new_file('myfile', root_id, 'myfile-text',
488
self.assertEqual(find_interesting(wt, wt, ['vfile']),
490
self.assertRaises(NotVersionedError, find_interesting, wt, wt,
983
if os.name == "posix" and sys.platform != "cygwin":
984
# posix filesystems fail on renaming if the readonly bit is set
985
osutils.make_readonly(self.wt.abspath('first-dir'))
986
elif os.name == "nt":
987
# windows filesystems fail on renaming open files
988
self.addCleanup(file(self.wt.abspath('myfile')).close)
990
self.skip("Don't know how to force a permissions error on rename")
991
# now transform to rename
992
rename_transform, root_id = self.get_transform()
993
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
994
dir_id = rename_transform.trans_id_file_id('first-id')
995
rename_transform.adjust_path('newname', dir_id, file_trans_id)
996
e = self.assertRaises(errors.TransformRenameFailed,
997
rename_transform.apply)
999
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1000
# to .../first-dir/newname: [Errno 13] Permission denied"
1001
# On windows looks like:
1002
# "Failed to rename .../work/myfile to
1003
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1004
# This test isn't concerned with exactly what the error looks like,
1005
# and the strerror will vary across OS and locales, but the assert
1006
# that the exeception attributes are what we expect
1007
self.assertEqual(e.errno, errno.EACCES)
1008
if os.name == "posix":
1009
self.assertEndsWith(e.to_path, "/first-dir/newname")
1011
self.assertEqual(os.path.basename(e.from_path), "myfile")
1013
def test_set_executability_order(self):
1014
"""Ensure that executability behaves the same, no matter what order.
1016
- create file and set executability simultaneously
1017
- create file and set executability afterward
1018
- unsetting the executability of a file whose executability has not been
1019
declared should throw an exception (this may happen when a
1020
merge attempts to create a file with a duplicate ID)
1022
transform, root = self.get_transform()
1023
wt = transform._tree
1025
self.addCleanup(wt.unlock)
1026
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
1028
sac = transform.new_file('set_after_creation', root,
1029
'Set after creation', 'sac')
1030
transform.set_executability(True, sac)
1031
uws = transform.new_file('unset_without_set', root, 'Unset badly',
1033
self.assertRaises(KeyError, transform.set_executability, None, uws)
1035
self.assertTrue(wt.is_executable('soc'))
1036
self.assertTrue(wt.is_executable('sac'))
1038
def test_preserve_mode(self):
1039
"""File mode is preserved when replacing content"""
1040
if sys.platform == 'win32':
1041
raise TestSkipped('chmod has no effect on win32')
1042
transform, root = self.get_transform()
1043
transform.new_file('file1', root, 'contents', 'file1-id', True)
1045
self.wt.lock_write()
1046
self.addCleanup(self.wt.unlock)
1047
self.assertTrue(self.wt.is_executable('file1-id'))
1048
transform, root = self.get_transform()
1049
file1_id = transform.trans_id_tree_file_id('file1-id')
1050
transform.delete_contents(file1_id)
1051
transform.create_file('contents2', file1_id)
1053
self.assertTrue(self.wt.is_executable('file1-id'))
1055
def test__set_mode_stats_correctly(self):
1056
"""_set_mode stats to determine file mode."""
1057
if sys.platform == 'win32':
1058
raise TestSkipped('chmod has no effect on win32')
1062
def instrumented_stat(path):
1063
stat_paths.append(path)
1064
return real_stat(path)
1066
transform, root = self.get_transform()
1068
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
1069
file_id='bar-id-1', executable=False)
1072
transform, root = self.get_transform()
1073
bar1_id = transform.trans_id_tree_path('bar')
1074
bar2_id = transform.trans_id_tree_path('bar2')
1076
os.stat = instrumented_stat
1077
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
1080
transform.finalize()
1082
bar1_abspath = self.wt.abspath('bar')
1083
self.assertEqual([bar1_abspath], stat_paths)
1085
def test_iter_changes(self):
1086
self.wt.set_root_id('eert_toor')
1087
transform, root = self.get_transform()
1088
transform.new_file('old', root, 'blah', 'id-1', True)
1090
transform, root = self.get_transform()
1092
self.assertEqual([], list(transform.iter_changes()))
1093
old = transform.trans_id_tree_file_id('id-1')
1094
transform.unversion_file(old)
1095
self.assertEqual([('id-1', ('old', None), False, (True, False),
1096
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1097
(True, True))], list(transform.iter_changes()))
1098
transform.new_directory('new', root, 'id-1')
1099
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
1100
('eert_toor', 'eert_toor'), ('old', 'new'),
1101
('file', 'directory'),
1102
(True, False))], list(transform.iter_changes()))
1104
transform.finalize()
1106
def test_iter_changes_new(self):
1107
self.wt.set_root_id('eert_toor')
1108
transform, root = self.get_transform()
1109
transform.new_file('old', root, 'blah')
1111
transform, root = self.get_transform()
1113
old = transform.trans_id_tree_path('old')
1114
transform.version_file('id-1', old)
1115
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
1116
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1117
(False, False))], list(transform.iter_changes()))
1119
transform.finalize()
1121
def test_iter_changes_modifications(self):
1122
self.wt.set_root_id('eert_toor')
1123
transform, root = self.get_transform()
1124
transform.new_file('old', root, 'blah', 'id-1')
1125
transform.new_file('new', root, 'blah')
1126
transform.new_directory('subdir', root, 'subdir-id')
1128
transform, root = self.get_transform()
1130
old = transform.trans_id_tree_path('old')
1131
subdir = transform.trans_id_tree_file_id('subdir-id')
1132
new = transform.trans_id_tree_path('new')
1133
self.assertEqual([], list(transform.iter_changes()))
1136
transform.delete_contents(old)
1137
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1138
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1139
(False, False))], list(transform.iter_changes()))
1142
transform.create_file('blah', old)
1143
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1144
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1145
(False, False))], list(transform.iter_changes()))
1146
transform.cancel_deletion(old)
1147
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1148
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1149
(False, False))], list(transform.iter_changes()))
1150
transform.cancel_creation(old)
1152
# move file_id to a different file
1153
self.assertEqual([], list(transform.iter_changes()))
1154
transform.unversion_file(old)
1155
transform.version_file('id-1', new)
1156
transform.adjust_path('old', root, new)
1157
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1158
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1159
(False, False))], list(transform.iter_changes()))
1160
transform.cancel_versioning(new)
1161
transform._removed_id = set()
1164
self.assertEqual([], list(transform.iter_changes()))
1165
transform.set_executability(True, old)
1166
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1167
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1168
(False, True))], list(transform.iter_changes()))
1169
transform.set_executability(None, old)
1172
self.assertEqual([], list(transform.iter_changes()))
1173
transform.adjust_path('new', root, old)
1174
transform._new_parent = {}
1175
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1176
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1177
(False, False))], list(transform.iter_changes()))
1178
transform._new_name = {}
1181
self.assertEqual([], list(transform.iter_changes()))
1182
transform.adjust_path('new', subdir, old)
1183
transform._new_name = {}
1184
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1185
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1186
('file', 'file'), (False, False))],
1187
list(transform.iter_changes()))
1188
transform._new_path = {}
1191
transform.finalize()
1193
def test_iter_changes_modified_bleed(self):
1194
self.wt.set_root_id('eert_toor')
1195
"""Modified flag should not bleed from one change to another"""
1196
# unfortunately, we have no guarantee that file1 (which is modified)
1197
# will be applied before file2. And if it's applied after file2, it
1198
# obviously can't bleed into file2's change output. But for now, it
1200
transform, root = self.get_transform()
1201
transform.new_file('file1', root, 'blah', 'id-1')
1202
transform.new_file('file2', root, 'blah', 'id-2')
1204
transform, root = self.get_transform()
1206
transform.delete_contents(transform.trans_id_file_id('id-1'))
1207
transform.set_executability(True,
1208
transform.trans_id_file_id('id-2'))
1209
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1210
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1211
('file', None), (False, False)),
1212
('id-2', (u'file2', u'file2'), False, (True, True),
1213
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1214
('file', 'file'), (False, True))],
1215
list(transform.iter_changes()))
1217
transform.finalize()
1219
def test_iter_changes_move_missing(self):
1220
"""Test moving ids with no files around"""
1221
self.wt.set_root_id('toor_eert')
1222
# Need two steps because versioning a non-existant file is a conflict.
1223
transform, root = self.get_transform()
1224
transform.new_directory('floater', root, 'floater-id')
1226
transform, root = self.get_transform()
1227
transform.delete_contents(transform.trans_id_tree_path('floater'))
1229
transform, root = self.get_transform()
1230
floater = transform.trans_id_tree_path('floater')
1232
transform.adjust_path('flitter', root, floater)
1233
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1234
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1235
(None, None), (False, False))], list(transform.iter_changes()))
1237
transform.finalize()
1239
def test_iter_changes_pointless(self):
1240
"""Ensure that no-ops are not treated as modifications"""
1241
self.wt.set_root_id('eert_toor')
1242
transform, root = self.get_transform()
1243
transform.new_file('old', root, 'blah', 'id-1')
1244
transform.new_directory('subdir', root, 'subdir-id')
1246
transform, root = self.get_transform()
1248
old = transform.trans_id_tree_path('old')
1249
subdir = transform.trans_id_tree_file_id('subdir-id')
1250
self.assertEqual([], list(transform.iter_changes()))
1251
transform.delete_contents(subdir)
1252
transform.create_directory(subdir)
1253
transform.set_executability(False, old)
1254
transform.unversion_file(old)
1255
transform.version_file('id-1', old)
1256
transform.adjust_path('old', root, old)
1257
self.assertEqual([], list(transform.iter_changes()))
1259
transform.finalize()
1261
def test_rename_count(self):
1262
transform, root = self.get_transform()
1263
transform.new_file('name1', root, 'contents')
1264
self.assertEqual(transform.rename_count, 0)
1266
self.assertEqual(transform.rename_count, 1)
1267
transform2, root = self.get_transform()
1268
transform2.adjust_path('name2', root,
1269
transform2.trans_id_tree_path('name1'))
1270
self.assertEqual(transform2.rename_count, 0)
1272
self.assertEqual(transform2.rename_count, 2)
1274
def test_change_parent(self):
1275
"""Ensure that after we change a parent, the results are still right.
1277
Renames and parent changes on pending transforms can happen as part
1278
of conflict resolution, and are explicitly permitted by the
1281
This test ensures they work correctly with the rename-avoidance
1284
transform, root = self.get_transform()
1285
parent1 = transform.new_directory('parent1', root)
1286
child1 = transform.new_file('child1', parent1, 'contents')
1287
parent2 = transform.new_directory('parent2', root)
1288
transform.adjust_path('child1', parent2, child1)
1290
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1291
self.assertPathExists(self.wt.abspath('parent2/child1'))
1292
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1293
# no rename for child1 (counting only renames during apply)
1294
self.assertEqual(2, transform.rename_count)
1296
def test_cancel_parent(self):
1297
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1299
This is like the test_change_parent, except that we cancel the parent
1300
before adjusting the path. The transform must detect that the
1301
directory is non-empty, and move children to safe locations.
1303
transform, root = self.get_transform()
1304
parent1 = transform.new_directory('parent1', root)
1305
child1 = transform.new_file('child1', parent1, 'contents')
1306
child2 = transform.new_file('child2', parent1, 'contents')
1308
transform.cancel_creation(parent1)
1310
self.fail('Failed to move child1 before deleting parent1')
1311
transform.cancel_creation(child2)
1312
transform.create_directory(parent1)
1314
transform.cancel_creation(parent1)
1315
# If the transform incorrectly believes that child2 is still in
1316
# parent1's limbo directory, it will try to rename it and fail
1317
# because was already moved by the first cancel_creation.
1319
self.fail('Transform still thinks child2 is a child of parent1')
1320
parent2 = transform.new_directory('parent2', root)
1321
transform.adjust_path('child1', parent2, child1)
1323
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1324
self.assertPathExists(self.wt.abspath('parent2/child1'))
1325
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1326
self.assertEqual(2, transform.rename_count)
1328
def test_adjust_and_cancel(self):
1329
"""Make sure adjust_path keeps track of limbo children properly"""
1330
transform, root = self.get_transform()
1331
parent1 = transform.new_directory('parent1', root)
1332
child1 = transform.new_file('child1', parent1, 'contents')
1333
parent2 = transform.new_directory('parent2', root)
1334
transform.adjust_path('child1', parent2, child1)
1335
transform.cancel_creation(child1)
1337
transform.cancel_creation(parent1)
1338
# if the transform thinks child1 is still in parent1's limbo
1339
# directory, it will attempt to move it and fail.
1341
self.fail('Transform still thinks child1 is a child of parent1')
1342
transform.finalize()
1344
def test_noname_contents(self):
1345
"""TreeTransform should permit deferring naming files."""
1346
transform, root = self.get_transform()
1347
parent = transform.trans_id_file_id('parent-id')
1349
transform.create_directory(parent)
1351
self.fail("Can't handle contents with no name")
1352
transform.finalize()
1354
def test_noname_contents_nested(self):
1355
"""TreeTransform should permit deferring naming files."""
1356
transform, root = self.get_transform()
1357
parent = transform.trans_id_file_id('parent-id')
1359
transform.create_directory(parent)
1361
self.fail("Can't handle contents with no name")
1362
child = transform.new_directory('child', parent)
1363
transform.adjust_path('parent', root, parent)
1365
self.assertPathExists(self.wt.abspath('parent/child'))
1366
self.assertEqual(1, transform.rename_count)
1368
def test_reuse_name(self):
1369
"""Avoid reusing the same limbo name for different files"""
1370
transform, root = self.get_transform()
1371
parent = transform.new_directory('parent', root)
1372
child1 = transform.new_directory('child', parent)
1374
child2 = transform.new_directory('child', parent)
1376
self.fail('Tranform tried to use the same limbo name twice')
1377
transform.adjust_path('child2', parent, child2)
1379
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1380
# child2 is put into top-level limbo because child1 has already
1381
# claimed the direct limbo path when child2 is created. There is no
1382
# advantage in renaming files once they're in top-level limbo, except
1384
self.assertEqual(2, transform.rename_count)
1386
def test_reuse_when_first_moved(self):
1387
"""Don't avoid direct paths when it is safe to use them"""
1388
transform, root = self.get_transform()
1389
parent = transform.new_directory('parent', root)
1390
child1 = transform.new_directory('child', parent)
1391
transform.adjust_path('child1', parent, child1)
1392
child2 = transform.new_directory('child', parent)
1394
# limbo/new-1 => parent
1395
self.assertEqual(1, transform.rename_count)
1397
def test_reuse_after_cancel(self):
1398
"""Don't avoid direct paths when it is safe to use them"""
1399
transform, root = self.get_transform()
1400
parent2 = transform.new_directory('parent2', root)
1401
child1 = transform.new_directory('child1', parent2)
1402
transform.cancel_creation(parent2)
1403
transform.create_directory(parent2)
1404
child2 = transform.new_directory('child1', parent2)
1405
transform.adjust_path('child2', parent2, child1)
1407
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1408
self.assertEqual(2, transform.rename_count)
1410
def test_finalize_order(self):
1411
"""Finalize must be done in child-to-parent order"""
1412
transform, root = self.get_transform()
1413
parent = transform.new_directory('parent', root)
1414
child = transform.new_directory('child', parent)
1416
transform.finalize()
1418
self.fail('Tried to remove parent before child1')
1420
def test_cancel_with_cancelled_child_should_succeed(self):
1421
transform, root = self.get_transform()
1422
parent = transform.new_directory('parent', root)
1423
child = transform.new_directory('child', parent)
1424
transform.cancel_creation(child)
1425
transform.cancel_creation(parent)
1426
transform.finalize()
1428
def test_rollback_on_directory_clash(self):
1430
wt = self.make_branch_and_tree('.')
1431
tt = TreeTransform(wt) # TreeTransform obtains write lock
1433
foo = tt.new_directory('foo', tt.root)
1434
tt.new_file('bar', foo, 'foobar')
1435
baz = tt.new_directory('baz', tt.root)
1436
tt.new_file('qux', baz, 'quux')
1437
# Ask for a rename 'foo' -> 'baz'
1438
tt.adjust_path('baz', tt.root, foo)
1439
# Lie to tt that we've already resolved all conflicts.
1440
tt.apply(no_conflicts=True)
1444
# The rename will fail because the target directory is not empty (but
1445
# raises FileExists anyway).
1446
err = self.assertRaises(errors.FileExists, tt_helper)
1447
self.assertContainsRe(str(err),
1448
"^File exists: .+/baz")
1450
def test_two_directories_clash(self):
1452
wt = self.make_branch_and_tree('.')
1453
tt = TreeTransform(wt) # TreeTransform obtains write lock
1455
foo_1 = tt.new_directory('foo', tt.root)
1456
tt.new_directory('bar', foo_1)
1457
# Adding the same directory with a different content
1458
foo_2 = tt.new_directory('foo', tt.root)
1459
tt.new_directory('baz', foo_2)
1460
# Lie to tt that we've already resolved all conflicts.
1461
tt.apply(no_conflicts=True)
1465
err = self.assertRaises(errors.FileExists, tt_helper)
1466
self.assertContainsRe(str(err),
1467
"^File exists: .+/foo")
1469
def test_two_directories_clash_finalize(self):
1471
wt = self.make_branch_and_tree('.')
1472
tt = TreeTransform(wt) # TreeTransform obtains write lock
1474
foo_1 = tt.new_directory('foo', tt.root)
1475
tt.new_directory('bar', foo_1)
1476
# Adding the same directory with a different content
1477
foo_2 = tt.new_directory('foo', tt.root)
1478
tt.new_directory('baz', foo_2)
1479
# Lie to tt that we've already resolved all conflicts.
1480
tt.apply(no_conflicts=True)
1484
err = self.assertRaises(errors.FileExists, tt_helper)
1485
self.assertContainsRe(str(err),
1486
"^File exists: .+/foo")
1488
def test_file_to_directory(self):
1489
wt = self.make_branch_and_tree('.')
1490
self.build_tree(['foo'])
1493
tt = TreeTransform(wt)
1494
self.addCleanup(tt.finalize)
1495
foo_trans_id = tt.trans_id_tree_path("foo")
1496
tt.delete_contents(foo_trans_id)
1497
tt.create_directory(foo_trans_id)
1498
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1499
tt.create_file(["aa\n"], bar_trans_id)
1500
tt.version_file("bar-1", bar_trans_id)
1502
self.assertPathExists("foo/bar")
1505
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1510
changes = wt.changes_from(wt.basis_tree())
1511
self.assertFalse(changes.has_changed(), changes)
1513
def test_file_to_symlink(self):
1514
self.requireFeature(SymlinkFeature)
1515
wt = self.make_branch_and_tree('.')
1516
self.build_tree(['foo'])
1519
tt = TreeTransform(wt)
1520
self.addCleanup(tt.finalize)
1521
foo_trans_id = tt.trans_id_tree_path("foo")
1522
tt.delete_contents(foo_trans_id)
1523
tt.create_symlink("bar", foo_trans_id)
1525
self.assertPathExists("foo")
1527
self.addCleanup(wt.unlock)
1528
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1531
def test_dir_to_file(self):
1532
wt = self.make_branch_and_tree('.')
1533
self.build_tree(['foo/', 'foo/bar'])
1534
wt.add(['foo', 'foo/bar'])
1536
tt = TreeTransform(wt)
1537
self.addCleanup(tt.finalize)
1538
foo_trans_id = tt.trans_id_tree_path("foo")
1539
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1540
tt.delete_contents(foo_trans_id)
1541
tt.delete_versioned(bar_trans_id)
1542
tt.create_file(["aa\n"], foo_trans_id)
1544
self.assertPathExists("foo")
1546
self.addCleanup(wt.unlock)
1547
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1550
def test_dir_to_hardlink(self):
1551
self.requireFeature(HardlinkFeature)
1552
wt = self.make_branch_and_tree('.')
1553
self.build_tree(['foo/', 'foo/bar'])
1554
wt.add(['foo', 'foo/bar'])
1556
tt = TreeTransform(wt)
1557
self.addCleanup(tt.finalize)
1558
foo_trans_id = tt.trans_id_tree_path("foo")
1559
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1560
tt.delete_contents(foo_trans_id)
1561
tt.delete_versioned(bar_trans_id)
1562
self.build_tree(['baz'])
1563
tt.create_hardlink("baz", foo_trans_id)
1565
self.assertPathExists("foo")
1566
self.assertPathExists("baz")
1568
self.addCleanup(wt.unlock)
1569
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1572
def test_no_final_path(self):
1573
transform, root = self.get_transform()
1574
trans_id = transform.trans_id_file_id('foo')
1575
transform.create_file('bar', trans_id)
1576
transform.cancel_creation(trans_id)
1579
def test_create_from_tree(self):
1580
tree1 = self.make_branch_and_tree('tree1')
1581
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1582
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1583
tree2 = self.make_branch_and_tree('tree2')
1584
tt = TreeTransform(tree2)
1585
foo_trans_id = tt.create_path('foo', tt.root)
1586
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1587
bar_trans_id = tt.create_path('bar', tt.root)
1588
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1590
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1591
self.assertFileEqual('baz', 'tree2/bar')
1593
def test_create_from_tree_bytes(self):
1594
"""Provided lines are used instead of tree content."""
1595
tree1 = self.make_branch_and_tree('tree1')
1596
self.build_tree_contents([('tree1/foo', 'bar'),])
1597
tree1.add('foo', 'foo-id')
1598
tree2 = self.make_branch_and_tree('tree2')
1599
tt = TreeTransform(tree2)
1600
foo_trans_id = tt.create_path('foo', tt.root)
1601
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1603
self.assertFileEqual('qux', 'tree2/foo')
1605
def test_create_from_tree_symlink(self):
1606
self.requireFeature(SymlinkFeature)
1607
tree1 = self.make_branch_and_tree('tree1')
1608
os.symlink('bar', 'tree1/foo')
1609
tree1.add('foo', 'foo-id')
1610
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1611
foo_trans_id = tt.create_path('foo', tt.root)
1612
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1614
self.assertEqual('bar', os.readlink('tree2/foo'))
494
1617
class TransformGroup(object):
495
def __init__(self, dirname):
1619
def __init__(self, dirname, root_id):
496
1620
self.name = dirname
497
1621
os.mkdir(dirname)
498
1622
self.wt = BzrDir.create_standalone_workingtree(dirname)
1623
self.wt.set_root_id(root_id)
499
1624
self.b = self.wt.branch
500
1625
self.tt = TreeTransform(self.wt)
501
1626
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
503
1629
def conflict_text(tree, merge):
504
1630
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
505
1631
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1634
class TestInventoryAltered(tests.TestCaseWithTransport):
1636
def test_inventory_altered_unchanged(self):
1637
tree = self.make_branch_and_tree('tree')
1638
self.build_tree(['tree/foo'])
1639
tree.add('foo', 'foo-id')
1640
with TransformPreview(tree) as tt:
1641
self.assertEqual([], tt._inventory_altered())
1643
def test_inventory_altered_changed_parent_id(self):
1644
tree = self.make_branch_and_tree('tree')
1645
self.build_tree(['tree/foo'])
1646
tree.add('foo', 'foo-id')
1647
with TransformPreview(tree) as tt:
1648
tt.unversion_file(tt.root)
1649
tt.version_file('new-id', tt.root)
1650
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1651
foo_tuple = ('foo', foo_trans_id)
1652
root_tuple = ('', tt.root)
1653
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1655
def test_inventory_altered_noop_changed_parent_id(self):
1656
tree = self.make_branch_and_tree('tree')
1657
self.build_tree(['tree/foo'])
1658
tree.add('foo', 'foo-id')
1659
with TransformPreview(tree) as tt:
1660
tt.unversion_file(tt.root)
1661
tt.version_file(tree.get_root_id(), tt.root)
1662
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1663
self.assertEqual([], tt._inventory_altered())
508
1666
class TestTransformMerge(TestCaseInTempDir):
509
1668
def test_text_merge(self):
510
base = TransformGroup("base")
1669
root_id = generate_ids.gen_root_id()
1670
base = TransformGroup("base", root_id)
511
1671
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
512
1672
base.tt.new_file('b', base.root, 'b1', 'b')
513
1673
base.tt.new_file('c', base.root, 'c', 'c')
698
1862
a.add(['foo', 'foo/bar', 'foo/baz'])
699
1863
a.commit('initial commit')
700
1864
b = BzrDir.create_standalone_workingtree('b')
701
build_tree(a.basis_tree(), b)
1865
basis = a.basis_tree()
1867
self.addCleanup(basis.unlock)
1868
build_tree(basis, b)
702
1869
self.assertIs(os.path.isdir('b/foo'), True)
703
1870
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
704
1871
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
706
class MockTransform(object):
708
def has_named_child(self, by_parent, parent_id, name):
709
for child_id in by_parent[parent_id]:
713
elif name == "name.~%s~" % child_id:
717
class MockEntry(object):
719
object.__init__(self)
722
class TestGetBackupName(TestCase):
723
def test_get_backup_name(self):
1873
def test_build_with_references(self):
1874
tree = self.make_branch_and_tree('source',
1875
format='dirstate-with-subtree')
1876
subtree = self.make_branch_and_tree('source/subtree',
1877
format='dirstate-with-subtree')
1878
tree.add_reference(subtree)
1879
tree.commit('a revision')
1880
tree.branch.create_checkout('target')
1881
self.assertPathExists('target')
1882
self.assertPathExists('target/subtree')
1884
def test_file_conflict_handling(self):
1885
"""Ensure that when building trees, conflict handling is done"""
1886
source = self.make_branch_and_tree('source')
1887
target = self.make_branch_and_tree('target')
1888
self.build_tree(['source/file', 'target/file'])
1889
source.add('file', 'new-file')
1890
source.commit('added file')
1891
build_tree(source.basis_tree(), target)
1892
self.assertEqual([DuplicateEntry('Moved existing file to',
1893
'file.moved', 'file', None, 'new-file')],
1895
target2 = self.make_branch_and_tree('target2')
1896
target_file = file('target2/file', 'wb')
1898
source_file = file('source/file', 'rb')
1900
target_file.write(source_file.read())
1905
build_tree(source.basis_tree(), target2)
1906
self.assertEqual([], target2.conflicts())
1908
def test_symlink_conflict_handling(self):
1909
"""Ensure that when building trees, conflict handling is done"""
1910
self.requireFeature(SymlinkFeature)
1911
source = self.make_branch_and_tree('source')
1912
os.symlink('foo', 'source/symlink')
1913
source.add('symlink', 'new-symlink')
1914
source.commit('added file')
1915
target = self.make_branch_and_tree('target')
1916
os.symlink('bar', 'target/symlink')
1917
build_tree(source.basis_tree(), target)
1918
self.assertEqual([DuplicateEntry('Moved existing file to',
1919
'symlink.moved', 'symlink', None, 'new-symlink')],
1921
target = self.make_branch_and_tree('target2')
1922
os.symlink('foo', 'target2/symlink')
1923
build_tree(source.basis_tree(), target)
1924
self.assertEqual([], target.conflicts())
1926
def test_directory_conflict_handling(self):
1927
"""Ensure that when building trees, conflict handling is done"""
1928
source = self.make_branch_and_tree('source')
1929
target = self.make_branch_and_tree('target')
1930
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1931
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1932
source.commit('added file')
1933
build_tree(source.basis_tree(), target)
1934
self.assertEqual([], target.conflicts())
1935
self.assertPathExists('target/dir1/file')
1937
# Ensure contents are merged
1938
target = self.make_branch_and_tree('target2')
1939
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1940
build_tree(source.basis_tree(), target)
1941
self.assertEqual([], target.conflicts())
1942
self.assertPathExists('target2/dir1/file2')
1943
self.assertPathExists('target2/dir1/file')
1945
# Ensure new contents are suppressed for existing branches
1946
target = self.make_branch_and_tree('target3')
1947
self.make_branch('target3/dir1')
1948
self.build_tree(['target3/dir1/file2'])
1949
build_tree(source.basis_tree(), target)
1950
self.assertPathDoesNotExist('target3/dir1/file')
1951
self.assertPathExists('target3/dir1/file2')
1952
self.assertPathExists('target3/dir1.diverted/file')
1953
self.assertEqual([DuplicateEntry('Diverted to',
1954
'dir1.diverted', 'dir1', 'new-dir1', None)],
1957
target = self.make_branch_and_tree('target4')
1958
self.build_tree(['target4/dir1/'])
1959
self.make_branch('target4/dir1/file')
1960
build_tree(source.basis_tree(), target)
1961
self.assertPathExists('target4/dir1/file')
1962
self.assertEqual('directory', file_kind('target4/dir1/file'))
1963
self.assertPathExists('target4/dir1/file.diverted')
1964
self.assertEqual([DuplicateEntry('Diverted to',
1965
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1968
def test_mixed_conflict_handling(self):
1969
"""Ensure that when building trees, conflict handling is done"""
1970
source = self.make_branch_and_tree('source')
1971
target = self.make_branch_and_tree('target')
1972
self.build_tree(['source/name', 'target/name/'])
1973
source.add('name', 'new-name')
1974
source.commit('added file')
1975
build_tree(source.basis_tree(), target)
1976
self.assertEqual([DuplicateEntry('Moved existing file to',
1977
'name.moved', 'name', None, 'new-name')], target.conflicts())
1979
def test_raises_in_populated(self):
1980
source = self.make_branch_and_tree('source')
1981
self.build_tree(['source/name'])
1983
source.commit('added name')
1984
target = self.make_branch_and_tree('target')
1985
self.build_tree(['target/name'])
1987
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1988
build_tree, source.basis_tree(), target)
1990
def test_build_tree_rename_count(self):
1991
source = self.make_branch_and_tree('source')
1992
self.build_tree(['source/file1', 'source/dir1/'])
1993
source.add(['file1', 'dir1'])
1994
source.commit('add1')
1995
target1 = self.make_branch_and_tree('target1')
1996
transform_result = build_tree(source.basis_tree(), target1)
1997
self.assertEqual(2, transform_result.rename_count)
1999
self.build_tree(['source/dir1/file2'])
2000
source.add(['dir1/file2'])
2001
source.commit('add3')
2002
target2 = self.make_branch_and_tree('target2')
2003
transform_result = build_tree(source.basis_tree(), target2)
2004
# children of non-root directories should not be renamed
2005
self.assertEqual(2, transform_result.rename_count)
2007
def create_ab_tree(self):
2008
"""Create a committed test tree with two files"""
2009
source = self.make_branch_and_tree('source')
2010
self.build_tree_contents([('source/file1', 'A')])
2011
self.build_tree_contents([('source/file2', 'B')])
2012
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2013
source.commit('commit files')
2015
self.addCleanup(source.unlock)
2018
def test_build_tree_accelerator_tree(self):
2019
source = self.create_ab_tree()
2020
self.build_tree_contents([('source/file2', 'C')])
2022
real_source_get_file = source.get_file
2023
def get_file(file_id, path=None):
2024
calls.append(file_id)
2025
return real_source_get_file(file_id, path)
2026
source.get_file = get_file
2027
target = self.make_branch_and_tree('target')
2028
revision_tree = source.basis_tree()
2029
revision_tree.lock_read()
2030
self.addCleanup(revision_tree.unlock)
2031
build_tree(revision_tree, target, source)
2032
self.assertEqual(['file1-id'], calls)
2034
self.addCleanup(target.unlock)
2035
self.assertEqual([], list(target.iter_changes(revision_tree)))
2037
def test_build_tree_accelerator_tree_observes_sha1(self):
2038
source = self.create_ab_tree()
2039
sha1 = osutils.sha_string('A')
2040
target = self.make_branch_and_tree('target')
2042
self.addCleanup(target.unlock)
2043
state = target.current_dirstate()
2044
state._cutoff_time = time.time() + 60
2045
build_tree(source.basis_tree(), target, source)
2046
entry = state._get_entry(0, path_utf8='file1')
2047
self.assertEqual(sha1, entry[1][0][1])
2049
def test_build_tree_accelerator_tree_missing_file(self):
2050
source = self.create_ab_tree()
2051
os.unlink('source/file1')
2052
source.remove(['file2'])
2053
target = self.make_branch_and_tree('target')
2054
revision_tree = source.basis_tree()
2055
revision_tree.lock_read()
2056
self.addCleanup(revision_tree.unlock)
2057
build_tree(revision_tree, target, source)
2059
self.addCleanup(target.unlock)
2060
self.assertEqual([], list(target.iter_changes(revision_tree)))
2062
def test_build_tree_accelerator_wrong_kind(self):
2063
self.requireFeature(SymlinkFeature)
2064
source = self.make_branch_and_tree('source')
2065
self.build_tree_contents([('source/file1', '')])
2066
self.build_tree_contents([('source/file2', '')])
2067
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2068
source.commit('commit files')
2069
os.unlink('source/file2')
2070
self.build_tree_contents([('source/file2/', 'C')])
2071
os.unlink('source/file1')
2072
os.symlink('file2', 'source/file1')
2074
real_source_get_file = source.get_file
2075
def get_file(file_id, path=None):
2076
calls.append(file_id)
2077
return real_source_get_file(file_id, path)
2078
source.get_file = get_file
2079
target = self.make_branch_and_tree('target')
2080
revision_tree = source.basis_tree()
2081
revision_tree.lock_read()
2082
self.addCleanup(revision_tree.unlock)
2083
build_tree(revision_tree, target, source)
2084
self.assertEqual([], calls)
2086
self.addCleanup(target.unlock)
2087
self.assertEqual([], list(target.iter_changes(revision_tree)))
2089
def test_build_tree_hardlink(self):
2090
self.requireFeature(HardlinkFeature)
2091
source = self.create_ab_tree()
2092
target = self.make_branch_and_tree('target')
2093
revision_tree = source.basis_tree()
2094
revision_tree.lock_read()
2095
self.addCleanup(revision_tree.unlock)
2096
build_tree(revision_tree, target, source, hardlink=True)
2098
self.addCleanup(target.unlock)
2099
self.assertEqual([], list(target.iter_changes(revision_tree)))
2100
source_stat = os.stat('source/file1')
2101
target_stat = os.stat('target/file1')
2102
self.assertEqual(source_stat, target_stat)
2104
# Explicitly disallowing hardlinks should prevent them.
2105
target2 = self.make_branch_and_tree('target2')
2106
build_tree(revision_tree, target2, source, hardlink=False)
2108
self.addCleanup(target2.unlock)
2109
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2110
source_stat = os.stat('source/file1')
2111
target2_stat = os.stat('target2/file1')
2112
self.assertNotEqual(source_stat, target2_stat)
2114
def test_build_tree_accelerator_tree_moved(self):
2115
source = self.make_branch_and_tree('source')
2116
self.build_tree_contents([('source/file1', 'A')])
2117
source.add(['file1'], ['file1-id'])
2118
source.commit('commit files')
2119
source.rename_one('file1', 'file2')
2121
self.addCleanup(source.unlock)
2122
target = self.make_branch_and_tree('target')
2123
revision_tree = source.basis_tree()
2124
revision_tree.lock_read()
2125
self.addCleanup(revision_tree.unlock)
2126
build_tree(revision_tree, target, source)
2128
self.addCleanup(target.unlock)
2129
self.assertEqual([], list(target.iter_changes(revision_tree)))
2131
def test_build_tree_hardlinks_preserve_execute(self):
2132
self.requireFeature(HardlinkFeature)
2133
source = self.create_ab_tree()
2134
tt = TreeTransform(source)
2135
trans_id = tt.trans_id_tree_file_id('file1-id')
2136
tt.set_executability(True, trans_id)
2138
self.assertTrue(source.is_executable('file1-id'))
2139
target = self.make_branch_and_tree('target')
2140
revision_tree = source.basis_tree()
2141
revision_tree.lock_read()
2142
self.addCleanup(revision_tree.unlock)
2143
build_tree(revision_tree, target, source, hardlink=True)
2145
self.addCleanup(target.unlock)
2146
self.assertEqual([], list(target.iter_changes(revision_tree)))
2147
self.assertTrue(source.is_executable('file1-id'))
2149
def install_rot13_content_filter(self, pattern):
2151
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2152
# below, but that looks a bit... hard to read even if it's exactly
2154
original_registry = filters._reset_registry()
2155
def restore_registry():
2156
filters._reset_registry(original_registry)
2157
self.addCleanup(restore_registry)
2158
def rot13(chunks, context=None):
2159
return [''.join(chunks).encode('rot13')]
2160
rot13filter = filters.ContentFilter(rot13, rot13)
2161
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
2162
os.mkdir(self.test_home_dir + '/.bazaar')
2163
rules_filename = self.test_home_dir + '/.bazaar/rules'
2164
f = open(rules_filename, 'wb')
2165
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2167
def uninstall_rules():
2168
os.remove(rules_filename)
2170
self.addCleanup(uninstall_rules)
2173
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2174
"""build_tree will not hardlink files that have content filtering rules
2175
applied to them (but will still hardlink other files from the same tree
2178
self.requireFeature(HardlinkFeature)
2179
self.install_rot13_content_filter('file1')
2180
source = self.create_ab_tree()
2181
target = self.make_branch_and_tree('target')
2182
revision_tree = source.basis_tree()
2183
revision_tree.lock_read()
2184
self.addCleanup(revision_tree.unlock)
2185
build_tree(revision_tree, target, source, hardlink=True)
2187
self.addCleanup(target.unlock)
2188
self.assertEqual([], list(target.iter_changes(revision_tree)))
2189
source_stat = os.stat('source/file1')
2190
target_stat = os.stat('target/file1')
2191
self.assertNotEqual(source_stat, target_stat)
2192
source_stat = os.stat('source/file2')
2193
target_stat = os.stat('target/file2')
2194
self.assertEqualStat(source_stat, target_stat)
2196
def test_case_insensitive_build_tree_inventory(self):
2197
if (tests.CaseInsensitiveFilesystemFeature.available()
2198
or tests.CaseInsCasePresFilenameFeature.available()):
2199
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2200
source = self.make_branch_and_tree('source')
2201
self.build_tree(['source/file', 'source/FILE'])
2202
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2203
source.commit('added files')
2204
# Don't try this at home, kids!
2205
# Force the tree to report that it is case insensitive
2206
target = self.make_branch_and_tree('target')
2207
target.case_sensitive = False
2208
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2209
self.assertEqual('file.moved', target.id2path('lower-id'))
2210
self.assertEqual('FILE', target.id2path('upper-id'))
2212
def test_build_tree_observes_sha(self):
2213
source = self.make_branch_and_tree('source')
2214
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2215
source.add(['file1', 'dir', 'dir/file2'],
2216
['file1-id', 'dir-id', 'file2-id'])
2217
source.commit('new files')
2218
target = self.make_branch_and_tree('target')
2220
self.addCleanup(target.unlock)
2221
# We make use of the fact that DirState caches its cutoff time. So we
2222
# set the 'safe' time to one minute in the future.
2223
state = target.current_dirstate()
2224
state._cutoff_time = time.time() + 60
2225
build_tree(source.basis_tree(), target)
2226
entry1_sha = osutils.sha_file_by_name('source/file1')
2227
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2228
# entry[1] is the state information, entry[1][0] is the state of the
2229
# working tree, entry[1][0][1] is the sha value for the current working
2231
entry1 = state._get_entry(0, path_utf8='file1')
2232
self.assertEqual(entry1_sha, entry1[1][0][1])
2233
# The 'size' field must also be set.
2234
self.assertEqual(25, entry1[1][0][2])
2235
entry1_state = entry1[1][0]
2236
entry2 = state._get_entry(0, path_utf8='dir/file2')
2237
self.assertEqual(entry2_sha, entry2[1][0][1])
2238
self.assertEqual(29, entry2[1][0][2])
2239
entry2_state = entry2[1][0]
2240
# Now, make sure that we don't have to re-read the content. The
2241
# packed_stat should match exactly.
2242
self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
2243
self.assertEqual(entry2_sha,
2244
target.get_file_sha1('file2-id', 'dir/file2'))
2245
self.assertEqual(entry1_state, entry1[1][0])
2246
self.assertEqual(entry2_state, entry2[1][0])
2249
class TestCommitTransform(tests.TestCaseWithTransport):
2251
def get_branch(self):
2252
tree = self.make_branch_and_tree('tree')
2254
self.addCleanup(tree.unlock)
2255
tree.commit('empty commit')
2258
def get_branch_and_transform(self):
2259
branch = self.get_branch()
2260
tt = TransformPreview(branch.basis_tree())
2261
self.addCleanup(tt.finalize)
2264
def test_commit_wrong_basis(self):
2265
branch = self.get_branch()
2266
basis = branch.repository.revision_tree(
2267
_mod_revision.NULL_REVISION)
2268
tt = TransformPreview(basis)
2269
self.addCleanup(tt.finalize)
2270
e = self.assertRaises(ValueError, tt.commit, branch, '')
2271
self.assertEqual('TreeTransform not based on branch basis: null:',
2274
def test_empy_commit(self):
2275
branch, tt = self.get_branch_and_transform()
2276
rev = tt.commit(branch, 'my message')
2277
self.assertEqual(2, branch.revno())
2278
repo = branch.repository
2279
self.assertEqual('my message', repo.get_revision(rev).message)
2281
def test_merge_parents(self):
2282
branch, tt = self.get_branch_and_transform()
2283
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2284
self.assertEqual(['rev1b', 'rev1c'],
2285
branch.basis_tree().get_parent_ids()[1:])
2287
def test_first_commit(self):
2288
branch = self.make_branch('branch')
2290
self.addCleanup(branch.unlock)
2291
tt = TransformPreview(branch.basis_tree())
2292
self.addCleanup(tt.finalize)
2293
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2294
rev = tt.commit(branch, 'my message')
2295
self.assertEqual([], branch.basis_tree().get_parent_ids())
2296
self.assertNotEqual(_mod_revision.NULL_REVISION,
2297
branch.last_revision())
2299
def test_first_commit_with_merge_parents(self):
2300
branch = self.make_branch('branch')
2302
self.addCleanup(branch.unlock)
2303
tt = TransformPreview(branch.basis_tree())
2304
self.addCleanup(tt.finalize)
2305
e = self.assertRaises(ValueError, tt.commit, branch,
2306
'my message', ['rev1b-id'])
2307
self.assertEqual('Cannot supply merge parents for first commit.',
2309
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2311
def test_add_files(self):
2312
branch, tt = self.get_branch_and_transform()
2313
tt.new_file('file', tt.root, 'contents', 'file-id')
2314
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2315
if SymlinkFeature.available():
2316
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2317
rev = tt.commit(branch, 'message')
2318
tree = branch.basis_tree()
2319
self.assertEqual('file', tree.id2path('file-id'))
2320
self.assertEqual('contents', tree.get_file_text('file-id'))
2321
self.assertEqual('dir', tree.id2path('dir-id'))
2322
if SymlinkFeature.available():
2323
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2324
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2326
def test_add_unversioned(self):
2327
branch, tt = self.get_branch_and_transform()
2328
tt.new_file('file', tt.root, 'contents')
2329
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2330
'message', strict=True)
2332
def test_modify_strict(self):
2333
branch, tt = self.get_branch_and_transform()
2334
tt.new_file('file', tt.root, 'contents', 'file-id')
2335
tt.commit(branch, 'message', strict=True)
2336
tt = TransformPreview(branch.basis_tree())
2337
self.addCleanup(tt.finalize)
2338
trans_id = tt.trans_id_file_id('file-id')
2339
tt.delete_contents(trans_id)
2340
tt.create_file('contents', trans_id)
2341
tt.commit(branch, 'message', strict=True)
2343
def test_commit_malformed(self):
2344
"""Committing a malformed transform should raise an exception.
2346
In this case, we are adding a file without adding its parent.
2348
branch, tt = self.get_branch_and_transform()
2349
parent_id = tt.trans_id_file_id('parent-id')
2350
tt.new_file('file', parent_id, 'contents', 'file-id')
2351
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2354
def test_commit_rich_revision_data(self):
2355
branch, tt = self.get_branch_and_transform()
2356
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2357
committer='me <me@example.com>',
2358
revprops={'foo': 'bar'}, revision_id='revid-1',
2359
authors=['Author1 <author1@example.com>',
2360
'Author2 <author2@example.com>',
2362
self.assertEqual('revid-1', rev_id)
2363
revision = branch.repository.get_revision(rev_id)
2364
self.assertEqual(1, revision.timestamp)
2365
self.assertEqual(43201, revision.timezone)
2366
self.assertEqual('me <me@example.com>', revision.committer)
2367
self.assertEqual(['Author1 <author1@example.com>',
2368
'Author2 <author2@example.com>'],
2369
revision.get_apparent_authors())
2370
del revision.properties['authors']
2371
self.assertEqual({'foo': 'bar',
2372
'branch-nick': 'tree'},
2373
revision.properties)
2375
def test_no_explicit_revprops(self):
2376
branch, tt = self.get_branch_and_transform()
2377
rev_id = tt.commit(branch, 'message', authors=[
2378
'Author1 <author1@example.com>',
2379
'Author2 <author2@example.com>', ])
2380
revision = branch.repository.get_revision(rev_id)
2381
self.assertEqual(['Author1 <author1@example.com>',
2382
'Author2 <author2@example.com>'],
2383
revision.get_apparent_authors())
2384
self.assertEqual('tree', revision.properties['branch-nick'])
2387
class TestBackupName(tests.TestCase):
2389
def test_deprecations(self):
2390
class MockTransform(object):
2392
def has_named_child(self, by_parent, parent_id, name):
2393
return name in by_parent.get(parent_id, [])
2395
class MockEntry(object):
2398
object.__init__(self)
724
2401
tt = MockTransform()
725
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
726
self.assertEqual(name, 'name.~1~')
727
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
728
self.assertEqual(name, 'name.~2~')
729
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
730
self.assertEqual(name, 'name.~1~')
731
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
732
self.assertEqual(name, 'name.~1~')
733
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
734
self.assertEqual(name, 'name.~4~')
2402
name1 = self.applyDeprecated(
2403
symbol_versioning.deprecated_in((2, 3, 0)),
2404
transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
2405
self.assertEqual('name.~1~', name1)
2406
name2 = self.applyDeprecated(
2407
symbol_versioning.deprecated_in((2, 3, 0)),
2408
transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
2409
self.assertEqual('name.~2~', name2)
2412
class TestFileMover(tests.TestCaseWithTransport):
2414
def test_file_mover(self):
2415
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2416
mover = _FileMover()
2417
mover.rename('a', 'q')
2418
self.assertPathExists('q')
2419
self.assertPathDoesNotExist('a')
2420
self.assertPathExists('q/b')
2421
self.assertPathExists('c')
2422
self.assertPathExists('c/d')
2424
def test_pre_delete_rollback(self):
2425
self.build_tree(['a/'])
2426
mover = _FileMover()
2427
mover.pre_delete('a', 'q')
2428
self.assertPathExists('q')
2429
self.assertPathDoesNotExist('a')
2431
self.assertPathDoesNotExist('q')
2432
self.assertPathExists('a')
2434
def test_apply_deletions(self):
2435
self.build_tree(['a/', 'b/'])
2436
mover = _FileMover()
2437
mover.pre_delete('a', 'q')
2438
mover.pre_delete('b', 'r')
2439
self.assertPathExists('q')
2440
self.assertPathExists('r')
2441
self.assertPathDoesNotExist('a')
2442
self.assertPathDoesNotExist('b')
2443
mover.apply_deletions()
2444
self.assertPathDoesNotExist('q')
2445
self.assertPathDoesNotExist('r')
2446
self.assertPathDoesNotExist('a')
2447
self.assertPathDoesNotExist('b')
2449
def test_file_mover_rollback(self):
2450
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2451
mover = _FileMover()
2452
mover.rename('c/d', 'c/f')
2453
mover.rename('c/e', 'c/d')
2455
mover.rename('a', 'c')
2456
except errors.FileExists, e:
2458
self.assertPathExists('a')
2459
self.assertPathExists('c/d')
2462
class Bogus(Exception):
2466
class TestTransformRollback(tests.TestCaseWithTransport):
2468
class ExceptionFileMover(_FileMover):
2470
def __init__(self, bad_source=None, bad_target=None):
2471
_FileMover.__init__(self)
2472
self.bad_source = bad_source
2473
self.bad_target = bad_target
2475
def rename(self, source, target):
2476
if (self.bad_source is not None and
2477
source.endswith(self.bad_source)):
2479
elif (self.bad_target is not None and
2480
target.endswith(self.bad_target)):
2483
_FileMover.rename(self, source, target)
2485
def test_rollback_rename(self):
2486
tree = self.make_branch_and_tree('.')
2487
self.build_tree(['a/', 'a/b'])
2488
tt = TreeTransform(tree)
2489
self.addCleanup(tt.finalize)
2490
a_id = tt.trans_id_tree_path('a')
2491
tt.adjust_path('c', tt.root, a_id)
2492
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2493
self.assertRaises(Bogus, tt.apply,
2494
_mover=self.ExceptionFileMover(bad_source='a'))
2495
self.assertPathExists('a')
2496
self.assertPathExists('a/b')
2498
self.assertPathExists('c')
2499
self.assertPathExists('c/d')
2501
def test_rollback_rename_into_place(self):
2502
tree = self.make_branch_and_tree('.')
2503
self.build_tree(['a/', 'a/b'])
2504
tt = TreeTransform(tree)
2505
self.addCleanup(tt.finalize)
2506
a_id = tt.trans_id_tree_path('a')
2507
tt.adjust_path('c', tt.root, a_id)
2508
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2509
self.assertRaises(Bogus, tt.apply,
2510
_mover=self.ExceptionFileMover(bad_target='c/d'))
2511
self.assertPathExists('a')
2512
self.assertPathExists('a/b')
2514
self.assertPathExists('c')
2515
self.assertPathExists('c/d')
2517
def test_rollback_deletion(self):
2518
tree = self.make_branch_and_tree('.')
2519
self.build_tree(['a/', 'a/b'])
2520
tt = TreeTransform(tree)
2521
self.addCleanup(tt.finalize)
2522
a_id = tt.trans_id_tree_path('a')
2523
tt.delete_contents(a_id)
2524
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2525
self.assertRaises(Bogus, tt.apply,
2526
_mover=self.ExceptionFileMover(bad_target='d'))
2527
self.assertPathExists('a')
2528
self.assertPathExists('a/b')
2531
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2532
"""Ensure treetransform creation errors can be safely cleaned up after"""
2534
def _override_globals_in_method(self, instance, method_name, globals):
2535
"""Replace method on instance with one with updated globals"""
2537
func = getattr(instance, method_name).im_func
2538
new_globals = dict(func.func_globals)
2539
new_globals.update(globals)
2540
new_func = types.FunctionType(func.func_code, new_globals,
2541
func.func_name, func.func_defaults)
2542
setattr(instance, method_name,
2543
types.MethodType(new_func, instance, instance.__class__))
2544
self.addCleanup(delattr, instance, method_name)
2547
def _fake_open_raises_before(name, mode):
2548
"""Like open() but raises before doing anything"""
2552
def _fake_open_raises_after(name, mode):
2553
"""Like open() but raises after creating file without returning"""
2554
open(name, mode).close()
2557
def create_transform_and_root_trans_id(self):
2558
"""Setup a transform creating a file in limbo"""
2559
tree = self.make_branch_and_tree('.')
2560
tt = TreeTransform(tree)
2561
return tt, tt.create_path("a", tt.root)
2563
def create_transform_and_subdir_trans_id(self):
2564
"""Setup a transform creating a directory containing a file in limbo"""
2565
tree = self.make_branch_and_tree('.')
2566
tt = TreeTransform(tree)
2567
d_trans_id = tt.create_path("d", tt.root)
2568
tt.create_directory(d_trans_id)
2569
f_trans_id = tt.create_path("a", d_trans_id)
2570
tt.adjust_path("a", d_trans_id, f_trans_id)
2571
return tt, f_trans_id
2573
def test_root_create_file_open_raises_before_creation(self):
2574
tt, trans_id = self.create_transform_and_root_trans_id()
2575
self._override_globals_in_method(tt, "create_file",
2576
{"open": self._fake_open_raises_before})
2577
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2578
path = tt._limbo_name(trans_id)
2579
self.assertPathDoesNotExist(path)
2581
self.assertPathDoesNotExist(tt._limbodir)
2583
def test_root_create_file_open_raises_after_creation(self):
2584
tt, trans_id = self.create_transform_and_root_trans_id()
2585
self._override_globals_in_method(tt, "create_file",
2586
{"open": self._fake_open_raises_after})
2587
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2588
path = tt._limbo_name(trans_id)
2589
self.assertPathExists(path)
2591
self.assertPathDoesNotExist(path)
2592
self.assertPathDoesNotExist(tt._limbodir)
2594
def test_subdir_create_file_open_raises_before_creation(self):
2595
tt, trans_id = self.create_transform_and_subdir_trans_id()
2596
self._override_globals_in_method(tt, "create_file",
2597
{"open": self._fake_open_raises_before})
2598
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2599
path = tt._limbo_name(trans_id)
2600
self.assertPathDoesNotExist(path)
2602
self.assertPathDoesNotExist(tt._limbodir)
2604
def test_subdir_create_file_open_raises_after_creation(self):
2605
tt, trans_id = self.create_transform_and_subdir_trans_id()
2606
self._override_globals_in_method(tt, "create_file",
2607
{"open": self._fake_open_raises_after})
2608
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2609
path = tt._limbo_name(trans_id)
2610
self.assertPathExists(path)
2612
self.assertPathDoesNotExist(path)
2613
self.assertPathDoesNotExist(tt._limbodir)
2615
def test_rename_in_limbo_rename_raises_after_rename(self):
2616
tt, trans_id = self.create_transform_and_root_trans_id()
2617
parent1 = tt.new_directory('parent1', tt.root)
2618
child1 = tt.new_file('child1', parent1, 'contents')
2619
parent2 = tt.new_directory('parent2', tt.root)
2621
class FakeOSModule(object):
2622
def rename(self, old, new):
2625
self._override_globals_in_method(tt, "_rename_in_limbo",
2626
{"os": FakeOSModule()})
2628
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2629
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2630
self.assertPathExists(path)
2632
self.assertPathDoesNotExist(path)
2633
self.assertPathDoesNotExist(tt._limbodir)
2635
def test_rename_in_limbo_rename_raises_before_rename(self):
2636
tt, trans_id = self.create_transform_and_root_trans_id()
2637
parent1 = tt.new_directory('parent1', tt.root)
2638
child1 = tt.new_file('child1', parent1, 'contents')
2639
parent2 = tt.new_directory('parent2', tt.root)
2641
class FakeOSModule(object):
2642
def rename(self, old, new):
2644
self._override_globals_in_method(tt, "_rename_in_limbo",
2645
{"os": FakeOSModule()})
2647
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2648
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2649
self.assertPathExists(path)
2651
self.assertPathDoesNotExist(path)
2652
self.assertPathDoesNotExist(tt._limbodir)
2655
class TestTransformMissingParent(tests.TestCaseWithTransport):
2657
def make_tt_with_versioned_dir(self):
2658
wt = self.make_branch_and_tree('.')
2659
self.build_tree(['dir/',])
2660
wt.add(['dir'], ['dir-id'])
2661
wt.commit('Create dir')
2662
tt = TreeTransform(wt)
2663
self.addCleanup(tt.finalize)
2666
def test_resolve_create_parent_for_versioned_file(self):
2667
wt, tt = self.make_tt_with_versioned_dir()
2668
dir_tid = tt.trans_id_tree_file_id('dir-id')
2669
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2670
tt.delete_contents(dir_tid)
2671
tt.unversion_file(dir_tid)
2672
conflicts = resolve_conflicts(tt)
2673
# one conflict for the missing directory, one for the unversioned
2675
self.assertLength(2, conflicts)
2677
def test_non_versioned_file_create_conflict(self):
2678
wt, tt = self.make_tt_with_versioned_dir()
2679
dir_tid = tt.trans_id_tree_file_id('dir-id')
2680
tt.new_file('file', dir_tid, 'Contents')
2681
tt.delete_contents(dir_tid)
2682
tt.unversion_file(dir_tid)
2683
conflicts = resolve_conflicts(tt)
2684
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2685
self.assertLength(1, conflicts)
2686
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2690
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2691
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2693
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2694
('', ''), ('directory', 'directory'), (False, False))
2697
class TestTransformPreview(tests.TestCaseWithTransport):
2699
def create_tree(self):
2700
tree = self.make_branch_and_tree('.')
2701
self.build_tree_contents([('a', 'content 1')])
2702
tree.set_root_id('TREE_ROOT')
2703
tree.add('a', 'a-id')
2704
tree.commit('rev1', rev_id='rev1')
2705
return tree.branch.repository.revision_tree('rev1')
2707
def get_empty_preview(self):
2708
repository = self.make_repository('repo')
2709
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2710
preview = TransformPreview(tree)
2711
self.addCleanup(preview.finalize)
2714
def test_transform_preview(self):
2715
revision_tree = self.create_tree()
2716
preview = TransformPreview(revision_tree)
2717
self.addCleanup(preview.finalize)
2719
def test_transform_preview_tree(self):
2720
revision_tree = self.create_tree()
2721
preview = TransformPreview(revision_tree)
2722
self.addCleanup(preview.finalize)
2723
preview.get_preview_tree()
2725
def test_transform_new_file(self):
2726
revision_tree = self.create_tree()
2727
preview = TransformPreview(revision_tree)
2728
self.addCleanup(preview.finalize)
2729
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2730
preview_tree = preview.get_preview_tree()
2731
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2733
preview_tree.get_file('file2-id').read(), 'content B\n')
2735
def test_diff_preview_tree(self):
2736
revision_tree = self.create_tree()
2737
preview = TransformPreview(revision_tree)
2738
self.addCleanup(preview.finalize)
2739
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2740
preview_tree = preview.get_preview_tree()
2742
show_diff_trees(revision_tree, preview_tree, out)
2743
lines = out.getvalue().splitlines()
2744
self.assertEqual(lines[0], "=== added file 'file2'")
2745
# 3 lines of diff administrivia
2746
self.assertEqual(lines[4], "+content B")
2748
def test_transform_conflicts(self):
2749
revision_tree = self.create_tree()
2750
preview = TransformPreview(revision_tree)
2751
self.addCleanup(preview.finalize)
2752
preview.new_file('a', preview.root, 'content 2')
2753
resolve_conflicts(preview)
2754
trans_id = preview.trans_id_file_id('a-id')
2755
self.assertEqual('a.moved', preview.final_name(trans_id))
2757
def get_tree_and_preview_tree(self):
2758
revision_tree = self.create_tree()
2759
preview = TransformPreview(revision_tree)
2760
self.addCleanup(preview.finalize)
2761
a_trans_id = preview.trans_id_file_id('a-id')
2762
preview.delete_contents(a_trans_id)
2763
preview.create_file('b content', a_trans_id)
2764
preview_tree = preview.get_preview_tree()
2765
return revision_tree, preview_tree
2767
def test_iter_changes(self):
2768
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2769
root = revision_tree.inventory.root.file_id
2770
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2771
(root, root), ('a', 'a'), ('file', 'file'),
2773
list(preview_tree.iter_changes(revision_tree)))
2775
def test_include_unchanged_succeeds(self):
2776
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2777
changes = preview_tree.iter_changes(revision_tree,
2778
include_unchanged=True)
2779
root = revision_tree.inventory.root.file_id
2781
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2783
def test_specific_files(self):
2784
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2785
changes = preview_tree.iter_changes(revision_tree,
2786
specific_files=[''])
2787
self.assertEqual([A_ENTRY], list(changes))
2789
def test_want_unversioned(self):
2790
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2791
changes = preview_tree.iter_changes(revision_tree,
2792
want_unversioned=True)
2793
self.assertEqual([A_ENTRY], list(changes))
2795
def test_ignore_extra_trees_no_specific_files(self):
2796
# extra_trees is harmless without specific_files, so we'll silently
2797
# accept it, even though we won't use it.
2798
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2799
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2801
def test_ignore_require_versioned_no_specific_files(self):
2802
# require_versioned is meaningless without specific_files.
2803
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2804
preview_tree.iter_changes(revision_tree, require_versioned=False)
2806
def test_ignore_pb(self):
2807
# pb could be supported, but TT.iter_changes doesn't support it.
2808
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2809
preview_tree.iter_changes(revision_tree)
2811
def test_kind(self):
2812
revision_tree = self.create_tree()
2813
preview = TransformPreview(revision_tree)
2814
self.addCleanup(preview.finalize)
2815
preview.new_file('file', preview.root, 'contents', 'file-id')
2816
preview.new_directory('directory', preview.root, 'dir-id')
2817
preview_tree = preview.get_preview_tree()
2818
self.assertEqual('file', preview_tree.kind('file-id'))
2819
self.assertEqual('directory', preview_tree.kind('dir-id'))
2821
def test_get_file_mtime(self):
2822
preview = self.get_empty_preview()
2823
file_trans_id = preview.new_file('file', preview.root, 'contents',
2825
limbo_path = preview._limbo_name(file_trans_id)
2826
preview_tree = preview.get_preview_tree()
2827
self.assertEqual(os.stat(limbo_path).st_mtime,
2828
preview_tree.get_file_mtime('file-id'))
2830
def test_get_file_mtime_renamed(self):
2831
work_tree = self.make_branch_and_tree('tree')
2832
self.build_tree(['tree/file'])
2833
work_tree.add('file', 'file-id')
2834
preview = TransformPreview(work_tree)
2835
self.addCleanup(preview.finalize)
2836
file_trans_id = preview.trans_id_tree_file_id('file-id')
2837
preview.adjust_path('renamed', preview.root, file_trans_id)
2838
preview_tree = preview.get_preview_tree()
2839
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2840
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2842
def test_get_file_size(self):
2843
work_tree = self.make_branch_and_tree('tree')
2844
self.build_tree_contents([('tree/old', 'old')])
2845
work_tree.add('old', 'old-id')
2846
preview = TransformPreview(work_tree)
2847
self.addCleanup(preview.finalize)
2848
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2850
tree = preview.get_preview_tree()
2851
self.assertEqual(len('old'), tree.get_file_size('old-id'))
2852
self.assertEqual(len('contents'), tree.get_file_size('new-id'))
2854
def test_get_file(self):
2855
preview = self.get_empty_preview()
2856
preview.new_file('file', preview.root, 'contents', 'file-id')
2857
preview_tree = preview.get_preview_tree()
2858
tree_file = preview_tree.get_file('file-id')
2860
self.assertEqual('contents', tree_file.read())
2864
def test_get_symlink_target(self):
2865
self.requireFeature(SymlinkFeature)
2866
preview = self.get_empty_preview()
2867
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2868
preview_tree = preview.get_preview_tree()
2869
self.assertEqual('target',
2870
preview_tree.get_symlink_target('symlink-id'))
2872
def test_all_file_ids(self):
2873
tree = self.make_branch_and_tree('tree')
2874
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2875
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2876
preview = TransformPreview(tree)
2877
self.addCleanup(preview.finalize)
2878
preview.unversion_file(preview.trans_id_file_id('b-id'))
2879
c_trans_id = preview.trans_id_file_id('c-id')
2880
preview.unversion_file(c_trans_id)
2881
preview.version_file('c-id', c_trans_id)
2882
preview_tree = preview.get_preview_tree()
2883
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2884
preview_tree.all_file_ids())
2886
def test_path2id_deleted_unchanged(self):
2887
tree = self.make_branch_and_tree('tree')
2888
self.build_tree(['tree/unchanged', 'tree/deleted'])
2889
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2890
preview = TransformPreview(tree)
2891
self.addCleanup(preview.finalize)
2892
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2893
preview_tree = preview.get_preview_tree()
2894
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2895
self.assertIs(None, preview_tree.path2id('deleted'))
2897
def test_path2id_created(self):
2898
tree = self.make_branch_and_tree('tree')
2899
self.build_tree(['tree/unchanged'])
2900
tree.add(['unchanged'], ['unchanged-id'])
2901
preview = TransformPreview(tree)
2902
self.addCleanup(preview.finalize)
2903
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2904
'contents', 'new-id')
2905
preview_tree = preview.get_preview_tree()
2906
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2908
def test_path2id_moved(self):
2909
tree = self.make_branch_and_tree('tree')
2910
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2911
tree.add(['old_parent', 'old_parent/child'],
2912
['old_parent-id', 'child-id'])
2913
preview = TransformPreview(tree)
2914
self.addCleanup(preview.finalize)
2915
new_parent = preview.new_directory('new_parent', preview.root,
2917
preview.adjust_path('child', new_parent,
2918
preview.trans_id_file_id('child-id'))
2919
preview_tree = preview.get_preview_tree()
2920
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2921
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2923
def test_path2id_renamed_parent(self):
2924
tree = self.make_branch_and_tree('tree')
2925
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2926
tree.add(['old_name', 'old_name/child'],
2927
['parent-id', 'child-id'])
2928
preview = TransformPreview(tree)
2929
self.addCleanup(preview.finalize)
2930
preview.adjust_path('new_name', preview.root,
2931
preview.trans_id_file_id('parent-id'))
2932
preview_tree = preview.get_preview_tree()
2933
self.assertIs(None, preview_tree.path2id('old_name/child'))
2934
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2936
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2937
preview_tree = tt.get_preview_tree()
2938
preview_result = list(preview_tree.iter_entries_by_dir(
2942
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2943
self.assertEqual(actual_result, preview_result)
2945
def test_iter_entries_by_dir_new(self):
2946
tree = self.make_branch_and_tree('tree')
2947
tt = TreeTransform(tree)
2948
tt.new_file('new', tt.root, 'contents', 'new-id')
2949
self.assertMatchingIterEntries(tt)
2951
def test_iter_entries_by_dir_deleted(self):
2952
tree = self.make_branch_and_tree('tree')
2953
self.build_tree(['tree/deleted'])
2954
tree.add('deleted', 'deleted-id')
2955
tt = TreeTransform(tree)
2956
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2957
self.assertMatchingIterEntries(tt)
2959
def test_iter_entries_by_dir_unversioned(self):
2960
tree = self.make_branch_and_tree('tree')
2961
self.build_tree(['tree/removed'])
2962
tree.add('removed', 'removed-id')
2963
tt = TreeTransform(tree)
2964
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2965
self.assertMatchingIterEntries(tt)
2967
def test_iter_entries_by_dir_moved(self):
2968
tree = self.make_branch_and_tree('tree')
2969
self.build_tree(['tree/moved', 'tree/new_parent/'])
2970
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2971
tt = TreeTransform(tree)
2972
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2973
tt.trans_id_file_id('moved-id'))
2974
self.assertMatchingIterEntries(tt)
2976
def test_iter_entries_by_dir_specific_file_ids(self):
2977
tree = self.make_branch_and_tree('tree')
2978
tree.set_root_id('tree-root-id')
2979
self.build_tree(['tree/parent/', 'tree/parent/child'])
2980
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2981
tt = TreeTransform(tree)
2982
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2984
def test_symlink_content_summary(self):
2985
self.requireFeature(SymlinkFeature)
2986
preview = self.get_empty_preview()
2987
preview.new_symlink('path', preview.root, 'target', 'path-id')
2988
summary = preview.get_preview_tree().path_content_summary('path')
2989
self.assertEqual(('symlink', None, None, 'target'), summary)
2991
def test_missing_content_summary(self):
2992
preview = self.get_empty_preview()
2993
summary = preview.get_preview_tree().path_content_summary('path')
2994
self.assertEqual(('missing', None, None, None), summary)
2996
def test_deleted_content_summary(self):
2997
tree = self.make_branch_and_tree('tree')
2998
self.build_tree(['tree/path/'])
3000
preview = TransformPreview(tree)
3001
self.addCleanup(preview.finalize)
3002
preview.delete_contents(preview.trans_id_tree_path('path'))
3003
summary = preview.get_preview_tree().path_content_summary('path')
3004
self.assertEqual(('missing', None, None, None), summary)
3006
def test_file_content_summary_executable(self):
3007
preview = self.get_empty_preview()
3008
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
3009
preview.set_executability(True, path_id)
3010
summary = preview.get_preview_tree().path_content_summary('path')
3011
self.assertEqual(4, len(summary))
3012
self.assertEqual('file', summary[0])
3013
# size must be known
3014
self.assertEqual(len('contents'), summary[1])
3016
self.assertEqual(True, summary[2])
3017
# will not have hash (not cheap to determine)
3018
self.assertIs(None, summary[3])
3020
def test_change_executability(self):
3021
tree = self.make_branch_and_tree('tree')
3022
self.build_tree(['tree/path'])
3024
preview = TransformPreview(tree)
3025
self.addCleanup(preview.finalize)
3026
path_id = preview.trans_id_tree_path('path')
3027
preview.set_executability(True, path_id)
3028
summary = preview.get_preview_tree().path_content_summary('path')
3029
self.assertEqual(True, summary[2])
3031
def test_file_content_summary_non_exec(self):
3032
preview = self.get_empty_preview()
3033
preview.new_file('path', preview.root, 'contents', 'path-id')
3034
summary = preview.get_preview_tree().path_content_summary('path')
3035
self.assertEqual(4, len(summary))
3036
self.assertEqual('file', summary[0])
3037
# size must be known
3038
self.assertEqual(len('contents'), summary[1])
3040
self.assertEqual(False, summary[2])
3041
# will not have hash (not cheap to determine)
3042
self.assertIs(None, summary[3])
3044
def test_dir_content_summary(self):
3045
preview = self.get_empty_preview()
3046
preview.new_directory('path', preview.root, 'path-id')
3047
summary = preview.get_preview_tree().path_content_summary('path')
3048
self.assertEqual(('directory', None, None, None), summary)
3050
def test_tree_content_summary(self):
3051
preview = self.get_empty_preview()
3052
path = preview.new_directory('path', preview.root, 'path-id')
3053
preview.set_tree_reference('rev-1', path)
3054
summary = preview.get_preview_tree().path_content_summary('path')
3055
self.assertEqual(4, len(summary))
3056
self.assertEqual('tree-reference', summary[0])
3058
def test_annotate(self):
3059
tree = self.make_branch_and_tree('tree')
3060
self.build_tree_contents([('tree/file', 'a\n')])
3061
tree.add('file', 'file-id')
3062
tree.commit('a', rev_id='one')
3063
self.build_tree_contents([('tree/file', 'a\nb\n')])
3064
preview = TransformPreview(tree)
3065
self.addCleanup(preview.finalize)
3066
file_trans_id = preview.trans_id_file_id('file-id')
3067
preview.delete_contents(file_trans_id)
3068
preview.create_file('a\nb\nc\n', file_trans_id)
3069
preview_tree = preview.get_preview_tree()
3075
annotation = preview_tree.annotate_iter('file-id', 'me:')
3076
self.assertEqual(expected, annotation)
3078
def test_annotate_missing(self):
3079
preview = self.get_empty_preview()
3080
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3081
preview_tree = preview.get_preview_tree()
3087
annotation = preview_tree.annotate_iter('file-id', 'me:')
3088
self.assertEqual(expected, annotation)
3090
def test_annotate_rename(self):
3091
tree = self.make_branch_and_tree('tree')
3092
self.build_tree_contents([('tree/file', 'a\n')])
3093
tree.add('file', 'file-id')
3094
tree.commit('a', rev_id='one')
3095
preview = TransformPreview(tree)
3096
self.addCleanup(preview.finalize)
3097
file_trans_id = preview.trans_id_file_id('file-id')
3098
preview.adjust_path('newname', preview.root, file_trans_id)
3099
preview_tree = preview.get_preview_tree()
3103
annotation = preview_tree.annotate_iter('file-id', 'me:')
3104
self.assertEqual(expected, annotation)
3106
def test_annotate_deleted(self):
3107
tree = self.make_branch_and_tree('tree')
3108
self.build_tree_contents([('tree/file', 'a\n')])
3109
tree.add('file', 'file-id')
3110
tree.commit('a', rev_id='one')
3111
self.build_tree_contents([('tree/file', 'a\nb\n')])
3112
preview = TransformPreview(tree)
3113
self.addCleanup(preview.finalize)
3114
file_trans_id = preview.trans_id_file_id('file-id')
3115
preview.delete_contents(file_trans_id)
3116
preview_tree = preview.get_preview_tree()
3117
annotation = preview_tree.annotate_iter('file-id', 'me:')
3118
self.assertIs(None, annotation)
3120
def test_stored_kind(self):
3121
preview = self.get_empty_preview()
3122
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3123
preview_tree = preview.get_preview_tree()
3124
self.assertEqual('file', preview_tree.stored_kind('file-id'))
3126
def test_is_executable(self):
3127
preview = self.get_empty_preview()
3128
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3129
preview.set_executability(True, preview.trans_id_file_id('file-id'))
3130
preview_tree = preview.get_preview_tree()
3131
self.assertEqual(True, preview_tree.is_executable('file-id'))
3133
def test_get_set_parent_ids(self):
3134
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3135
self.assertEqual([], preview_tree.get_parent_ids())
3136
preview_tree.set_parent_ids(['rev-1'])
3137
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
3139
def test_plan_file_merge(self):
3140
work_a = self.make_branch_and_tree('wta')
3141
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3142
work_a.add('file', 'file-id')
3143
base_id = work_a.commit('base version')
3144
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3145
preview = TransformPreview(work_a)
3146
self.addCleanup(preview.finalize)
3147
trans_id = preview.trans_id_file_id('file-id')
3148
preview.delete_contents(trans_id)
3149
preview.create_file('b\nc\nd\ne\n', trans_id)
3150
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3151
tree_a = preview.get_preview_tree()
3152
tree_a.set_parent_ids([base_id])
3154
('killed-a', 'a\n'),
3155
('killed-b', 'b\n'),
3156
('unchanged', 'c\n'),
3157
('unchanged', 'd\n'),
3160
], list(tree_a.plan_file_merge('file-id', tree_b)))
3162
def test_plan_file_merge_revision_tree(self):
3163
work_a = self.make_branch_and_tree('wta')
3164
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3165
work_a.add('file', 'file-id')
3166
base_id = work_a.commit('base version')
3167
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3168
preview = TransformPreview(work_a.basis_tree())
3169
self.addCleanup(preview.finalize)
3170
trans_id = preview.trans_id_file_id('file-id')
3171
preview.delete_contents(trans_id)
3172
preview.create_file('b\nc\nd\ne\n', trans_id)
3173
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3174
tree_a = preview.get_preview_tree()
3175
tree_a.set_parent_ids([base_id])
3177
('killed-a', 'a\n'),
3178
('killed-b', 'b\n'),
3179
('unchanged', 'c\n'),
3180
('unchanged', 'd\n'),
3183
], list(tree_a.plan_file_merge('file-id', tree_b)))
3185
def test_walkdirs(self):
3186
preview = self.get_empty_preview()
3187
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3188
# FIXME: new_directory should mark root.
3189
preview.fixup_new_roots()
3190
preview_tree = preview.get_preview_tree()
3191
file_trans_id = preview.new_file('a', preview.root, 'contents',
3193
expected = [(('', 'tree-root'),
3194
[('a', 'a', 'file', None, 'a-id', 'file')])]
3195
self.assertEqual(expected, list(preview_tree.walkdirs()))
3197
def test_extras(self):
3198
work_tree = self.make_branch_and_tree('tree')
3199
self.build_tree(['tree/removed-file', 'tree/existing-file',
3200
'tree/not-removed-file'])
3201
work_tree.add(['removed-file', 'not-removed-file'])
3202
preview = TransformPreview(work_tree)
3203
self.addCleanup(preview.finalize)
3204
preview.new_file('new-file', preview.root, 'contents')
3205
preview.new_file('new-versioned-file', preview.root, 'contents',
3207
tree = preview.get_preview_tree()
3208
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3209
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
3212
def test_merge_into_preview(self):
3213
work_tree = self.make_branch_and_tree('tree')
3214
self.build_tree_contents([('tree/file','b\n')])
3215
work_tree.add('file', 'file-id')
3216
work_tree.commit('first commit')
3217
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
3218
self.build_tree_contents([('child/file','b\nc\n')])
3219
child_tree.commit('child commit')
3220
child_tree.lock_write()
3221
self.addCleanup(child_tree.unlock)
3222
work_tree.lock_write()
3223
self.addCleanup(work_tree.unlock)
3224
preview = TransformPreview(work_tree)
3225
self.addCleanup(preview.finalize)
3226
file_trans_id = preview.trans_id_file_id('file-id')
3227
preview.delete_contents(file_trans_id)
3228
preview.create_file('a\nb\n', file_trans_id)
3229
preview_tree = preview.get_preview_tree()
3230
merger = Merger.from_revision_ids(None, preview_tree,
3231
child_tree.branch.last_revision(),
3232
other_branch=child_tree.branch,
3233
tree_branch=work_tree.branch)
3234
merger.merge_type = Merge3Merger
3235
tt = merger.make_merger().make_preview_transform()
3236
self.addCleanup(tt.finalize)
3237
final_tree = tt.get_preview_tree()
3238
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3240
def test_merge_preview_into_workingtree(self):
3241
tree = self.make_branch_and_tree('tree')
3242
tree.set_root_id('TREE_ROOT')
3243
tt = TransformPreview(tree)
3244
self.addCleanup(tt.finalize)
3245
tt.new_file('name', tt.root, 'content', 'file-id')
3246
tree2 = self.make_branch_and_tree('tree2')
3247
tree2.set_root_id('TREE_ROOT')
3248
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3249
None, tree.basis_tree())
3250
merger.merge_type = Merge3Merger
3253
def test_merge_preview_into_workingtree_handles_conflicts(self):
3254
tree = self.make_branch_and_tree('tree')
3255
self.build_tree_contents([('tree/foo', 'bar')])
3256
tree.add('foo', 'foo-id')
3258
tt = TransformPreview(tree)
3259
self.addCleanup(tt.finalize)
3260
trans_id = tt.trans_id_file_id('foo-id')
3261
tt.delete_contents(trans_id)
3262
tt.create_file('baz', trans_id)
3263
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
3264
self.build_tree_contents([('tree2/foo', 'qux')])
3266
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3267
pb, tree.basis_tree())
3268
merger.merge_type = Merge3Merger
3271
def test_has_filename(self):
3272
wt = self.make_branch_and_tree('tree')
3273
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3274
tt = TransformPreview(wt)
3275
removed_id = tt.trans_id_tree_path('removed')
3276
tt.delete_contents(removed_id)
3277
tt.new_file('new', tt.root, 'contents')
3278
modified_id = tt.trans_id_tree_path('modified')
3279
tt.delete_contents(modified_id)
3280
tt.create_file('modified-contents', modified_id)
3281
self.addCleanup(tt.finalize)
3282
tree = tt.get_preview_tree()
3283
self.assertTrue(tree.has_filename('unmodified'))
3284
self.assertFalse(tree.has_filename('not-present'))
3285
self.assertFalse(tree.has_filename('removed'))
3286
self.assertTrue(tree.has_filename('new'))
3287
self.assertTrue(tree.has_filename('modified'))
3289
def test_is_executable(self):
3290
tree = self.make_branch_and_tree('tree')
3291
preview = TransformPreview(tree)
3292
self.addCleanup(preview.finalize)
3293
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3294
preview_tree = preview.get_preview_tree()
3295
self.assertEqual(False, preview_tree.is_executable('baz-id',
3297
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3299
def test_commit_preview_tree(self):
3300
tree = self.make_branch_and_tree('tree')
3301
rev_id = tree.commit('rev1')
3302
tree.branch.lock_write()
3303
self.addCleanup(tree.branch.unlock)
3304
tt = TransformPreview(tree)
3305
tt.new_file('file', tt.root, 'contents', 'file_id')
3306
self.addCleanup(tt.finalize)
3307
preview = tt.get_preview_tree()
3308
preview.set_parent_ids([rev_id])
3309
builder = tree.branch.get_commit_builder([rev_id])
3310
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3311
builder.finish_inventory()
3312
rev2_id = builder.commit('rev2')
3313
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3314
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3316
def test_ascii_limbo_paths(self):
3317
self.requireFeature(tests.UnicodeFilenameFeature)
3318
branch = self.make_branch('any')
3319
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3320
tt = TransformPreview(tree)
3321
self.addCleanup(tt.finalize)
3322
foo_id = tt.new_directory('', ROOT_PARENT)
3323
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3324
limbo_path = tt._limbo_name(bar_id)
3325
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3328
class FakeSerializer(object):
3329
"""Serializer implementation that simply returns the input.
3331
The input is returned in the order used by pack.ContainerPushParser.
3334
def bytes_record(bytes, names):
3338
class TestSerializeTransform(tests.TestCaseWithTransport):
3340
_test_needs_features = [tests.UnicodeFilenameFeature]
3342
def get_preview(self, tree=None):
3344
tree = self.make_branch_and_tree('tree')
3345
tt = TransformPreview(tree)
3346
self.addCleanup(tt.finalize)
3349
def assertSerializesTo(self, expected, tt):
3350
records = list(tt.serialize(FakeSerializer()))
3351
self.assertEqual(expected, records)
3354
def default_attribs():
3359
'_new_executability': {},
3361
'_tree_path_ids': {'': 'new-0'},
3363
'_removed_contents': [],
3364
'_non_present_ids': {},
3367
def make_records(self, attribs, contents):
3369
(((('attribs'),),), bencode.bencode(attribs))]
3370
records.extend([(((n, k),), c) for n, k, c in contents])
3373
def creation_records(self):
3374
attribs = self.default_attribs()
3375
attribs['_id_number'] = 3
3376
attribs['_new_name'] = {
3377
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3378
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3379
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3380
attribs['_new_executability'] = {'new-1': 1}
3382
('new-1', 'file', 'i 1\nbar\n'),
3383
('new-2', 'directory', ''),
3385
return self.make_records(attribs, contents)
3387
def test_serialize_creation(self):
3388
tt = self.get_preview()
3389
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3390
tt.new_directory('qux', tt.root, 'quxx')
3391
self.assertSerializesTo(self.creation_records(), tt)
3393
def test_deserialize_creation(self):
3394
tt = self.get_preview()
3395
tt.deserialize(iter(self.creation_records()))
3396
self.assertEqual(3, tt._id_number)
3397
self.assertEqual({'new-1': u'foo\u1234',
3398
'new-2': 'qux'}, tt._new_name)
3399
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3400
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3401
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3402
self.assertEqual({'new-1': True}, tt._new_executability)
3403
self.assertEqual({'new-1': 'file',
3404
'new-2': 'directory'}, tt._new_contents)
3405
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3407
foo_content = foo_limbo.read()
3410
self.assertEqual('bar', foo_content)
3412
def symlink_creation_records(self):
3413
attribs = self.default_attribs()
3414
attribs['_id_number'] = 2
3415
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3416
attribs['_new_parent'] = {'new-1': 'new-0'}
3417
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3418
return self.make_records(attribs, contents)
3420
def test_serialize_symlink_creation(self):
3421
self.requireFeature(tests.SymlinkFeature)
3422
tt = self.get_preview()
3423
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3424
self.assertSerializesTo(self.symlink_creation_records(), tt)
3426
def test_deserialize_symlink_creation(self):
3427
self.requireFeature(tests.SymlinkFeature)
3428
tt = self.get_preview()
3429
tt.deserialize(iter(self.symlink_creation_records()))
3430
abspath = tt._limbo_name('new-1')
3431
foo_content = osutils.readlink(abspath)
3432
self.assertEqual(u'bar\u1234', foo_content)
3434
def make_destruction_preview(self):
3435
tree = self.make_branch_and_tree('.')
3436
self.build_tree([u'foo\u1234', 'bar'])
3437
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3438
return self.get_preview(tree)
3440
def destruction_records(self):
3441
attribs = self.default_attribs()
3442
attribs['_id_number'] = 3
3443
attribs['_removed_id'] = ['new-1']
3444
attribs['_removed_contents'] = ['new-2']
3445
attribs['_tree_path_ids'] = {
3447
u'foo\u1234'.encode('utf-8'): 'new-1',
3450
return self.make_records(attribs, [])
3452
def test_serialize_destruction(self):
3453
tt = self.make_destruction_preview()
3454
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3455
tt.unversion_file(foo_trans_id)
3456
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3457
tt.delete_contents(bar_trans_id)
3458
self.assertSerializesTo(self.destruction_records(), tt)
3460
def test_deserialize_destruction(self):
3461
tt = self.make_destruction_preview()
3462
tt.deserialize(iter(self.destruction_records()))
3463
self.assertEqual({u'foo\u1234': 'new-1',
3465
'': tt.root}, tt._tree_path_ids)
3466
self.assertEqual({'new-1': u'foo\u1234',
3468
tt.root: ''}, tt._tree_id_paths)
3469
self.assertEqual(set(['new-1']), tt._removed_id)
3470
self.assertEqual(set(['new-2']), tt._removed_contents)
3472
def missing_records(self):
3473
attribs = self.default_attribs()
3474
attribs['_id_number'] = 2
3475
attribs['_non_present_ids'] = {
3477
return self.make_records(attribs, [])
3479
def test_serialize_missing(self):
3480
tt = self.get_preview()
3481
boo_trans_id = tt.trans_id_file_id('boo')
3482
self.assertSerializesTo(self.missing_records(), tt)
3484
def test_deserialize_missing(self):
3485
tt = self.get_preview()
3486
tt.deserialize(iter(self.missing_records()))
3487
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3489
def make_modification_preview(self):
3490
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3491
LINES_TWO = 'z\nbb\nx\ndd\n'
3492
tree = self.make_branch_and_tree('tree')
3493
self.build_tree_contents([('tree/file', LINES_ONE)])
3494
tree.add('file', 'file-id')
3495
return self.get_preview(tree), LINES_TWO
3497
def modification_records(self):
3498
attribs = self.default_attribs()
3499
attribs['_id_number'] = 2
3500
attribs['_tree_path_ids'] = {
3503
attribs['_removed_contents'] = ['new-1']
3504
contents = [('new-1', 'file',
3505
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3506
return self.make_records(attribs, contents)
3508
def test_serialize_modification(self):
3509
tt, LINES = self.make_modification_preview()
3510
trans_id = tt.trans_id_file_id('file-id')
3511
tt.delete_contents(trans_id)
3512
tt.create_file(LINES, trans_id)
3513
self.assertSerializesTo(self.modification_records(), tt)
3515
def test_deserialize_modification(self):
3516
tt, LINES = self.make_modification_preview()
3517
tt.deserialize(iter(self.modification_records()))
3518
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3520
def make_kind_change_preview(self):
3521
LINES = 'a\nb\nc\nd\n'
3522
tree = self.make_branch_and_tree('tree')
3523
self.build_tree(['tree/foo/'])
3524
tree.add('foo', 'foo-id')
3525
return self.get_preview(tree), LINES
3527
def kind_change_records(self):
3528
attribs = self.default_attribs()
3529
attribs['_id_number'] = 2
3530
attribs['_tree_path_ids'] = {
3533
attribs['_removed_contents'] = ['new-1']
3534
contents = [('new-1', 'file',
3535
'i 4\na\nb\nc\nd\n\n')]
3536
return self.make_records(attribs, contents)
3538
def test_serialize_kind_change(self):
3539
tt, LINES = self.make_kind_change_preview()
3540
trans_id = tt.trans_id_file_id('foo-id')
3541
tt.delete_contents(trans_id)
3542
tt.create_file(LINES, trans_id)
3543
self.assertSerializesTo(self.kind_change_records(), tt)
3545
def test_deserialize_kind_change(self):
3546
tt, LINES = self.make_kind_change_preview()
3547
tt.deserialize(iter(self.kind_change_records()))
3548
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3550
def make_add_contents_preview(self):
3551
LINES = 'a\nb\nc\nd\n'
3552
tree = self.make_branch_and_tree('tree')
3553
self.build_tree(['tree/foo'])
3555
os.unlink('tree/foo')
3556
return self.get_preview(tree), LINES
3558
def add_contents_records(self):
3559
attribs = self.default_attribs()
3560
attribs['_id_number'] = 2
3561
attribs['_tree_path_ids'] = {
3564
contents = [('new-1', 'file',
3565
'i 4\na\nb\nc\nd\n\n')]
3566
return self.make_records(attribs, contents)
3568
def test_serialize_add_contents(self):
3569
tt, LINES = self.make_add_contents_preview()
3570
trans_id = tt.trans_id_tree_path('foo')
3571
tt.create_file(LINES, trans_id)
3572
self.assertSerializesTo(self.add_contents_records(), tt)
3574
def test_deserialize_add_contents(self):
3575
tt, LINES = self.make_add_contents_preview()
3576
tt.deserialize(iter(self.add_contents_records()))
3577
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3579
def test_get_parents_lines(self):
3580
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3581
LINES_TWO = 'z\nbb\nx\ndd\n'
3582
tree = self.make_branch_and_tree('tree')
3583
self.build_tree_contents([('tree/file', LINES_ONE)])
3584
tree.add('file', 'file-id')
3585
tt = self.get_preview(tree)
3586
trans_id = tt.trans_id_tree_path('file')
3587
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3588
tt._get_parents_lines(trans_id))
3590
def test_get_parents_texts(self):
3591
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3592
LINES_TWO = 'z\nbb\nx\ndd\n'
3593
tree = self.make_branch_and_tree('tree')
3594
self.build_tree_contents([('tree/file', LINES_ONE)])
3595
tree.add('file', 'file-id')
3596
tt = self.get_preview(tree)
3597
trans_id = tt.trans_id_tree_path('file')
3598
self.assertEqual((LINES_ONE,),
3599
tt._get_parents_texts(trans_id))
3602
class TestOrphan(tests.TestCaseWithTransport):
3604
def test_no_orphan_for_transform_preview(self):
3605
tree = self.make_branch_and_tree('tree')
3606
tt = transform.TransformPreview(tree)
3607
self.addCleanup(tt.finalize)
3608
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3610
def _set_orphan_policy(self, wt, policy):
3611
wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
3614
def _prepare_orphan(self, wt):
3615
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3616
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3617
wt.commit('add dir and file ignoring foo')
3618
tt = transform.TreeTransform(wt)
3619
self.addCleanup(tt.finalize)
3620
# dir and bar are deleted
3621
dir_tid = tt.trans_id_tree_path('dir')
3622
file_tid = tt.trans_id_tree_path('dir/file')
3623
orphan_tid = tt.trans_id_tree_path('dir/foo')
3624
tt.delete_contents(file_tid)
3625
tt.unversion_file(file_tid)
3626
tt.delete_contents(dir_tid)
3627
tt.unversion_file(dir_tid)
3628
# There should be a conflict because dir still contain foo
3629
raw_conflicts = tt.find_conflicts()
3630
self.assertLength(1, raw_conflicts)
3631
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3632
return tt, orphan_tid
3634
def test_new_orphan_created(self):
3635
wt = self.make_branch_and_tree('.')
3636
self._set_orphan_policy(wt, 'move')
3637
tt, orphan_tid = self._prepare_orphan(wt)
3640
warnings.append(args[0] % args[1:])
3641
self.overrideAttr(trace, 'warning', warning)
3642
remaining_conflicts = resolve_conflicts(tt)
3643
self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
3645
# Yeah for resolved conflicts !
3646
self.assertLength(0, remaining_conflicts)
3647
# We have a new orphan
3648
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3649
self.assertEquals('bzr-orphans',
3650
tt.final_name(tt.final_parent(orphan_tid)))
3652
def test_never_orphan(self):
3653
wt = self.make_branch_and_tree('.')
3654
self._set_orphan_policy(wt, 'conflict')
3655
tt, orphan_tid = self._prepare_orphan(wt)
3656
remaining_conflicts = resolve_conflicts(tt)
3657
self.assertLength(1, remaining_conflicts)
3658
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3659
remaining_conflicts.pop())
3661
def test_orphan_error(self):
3662
def bogus_orphan(tt, orphan_id, parent_id):
3663
raise transform.OrphaningError(tt.final_name(orphan_id),
3664
tt.final_name(parent_id))
3665
transform.orphaning_registry.register('bogus', bogus_orphan,
3666
'Raise an error when orphaning')
3667
wt = self.make_branch_and_tree('.')
3668
self._set_orphan_policy(wt, 'bogus')
3669
tt, orphan_tid = self._prepare_orphan(wt)
3670
remaining_conflicts = resolve_conflicts(tt)
3671
self.assertLength(1, remaining_conflicts)
3672
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3673
remaining_conflicts.pop())
3675
def test_unknown_orphan_policy(self):
3676
wt = self.make_branch_and_tree('.')
3677
# Set a fictional policy nobody ever implemented
3678
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3679
tt, orphan_tid = self._prepare_orphan(wt)
3682
warnings.append(args[0] % args[1:])
3683
self.overrideAttr(trace, 'warning', warning)
3684
remaining_conflicts = resolve_conflicts(tt)
3685
# We fallback to the default policy which create a conflict
3686
self.assertLength(1, remaining_conflicts)
3687
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3688
remaining_conflicts.pop())
3689
self.assertLength(1, warnings)
3690
self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')