479
1006
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')
1009
def test_rename_fails(self):
1010
self.requireFeature(features.not_running_as_root)
1011
# see https://bugs.launchpad.net/bzr/+bug/491763
1012
create, root_id = self.get_transform()
1013
first_dir = create.new_directory('first-dir', root_id, 'first-id')
1014
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,
1017
if os.name == "posix" and sys.platform != "cygwin":
1018
# posix filesystems fail on renaming if the readonly bit is set
1019
osutils.make_readonly(self.wt.abspath('first-dir'))
1020
elif os.name == "nt":
1021
# windows filesystems fail on renaming open files
1022
self.addCleanup(file(self.wt.abspath('myfile')).close)
1024
self.skipTest("Can't force a permissions error on rename")
1025
# now transform to rename
1026
rename_transform, root_id = self.get_transform()
1027
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
1028
dir_id = rename_transform.trans_id_file_id('first-id')
1029
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1030
e = self.assertRaises(errors.TransformRenameFailed,
1031
rename_transform.apply)
1032
# On nix looks like:
1033
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1034
# to .../first-dir/newname: [Errno 13] Permission denied"
1035
# On windows looks like:
1036
# "Failed to rename .../work/myfile to
1037
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1038
# This test isn't concerned with exactly what the error looks like,
1039
# and the strerror will vary across OS and locales, but the assert
1040
# that the exeception attributes are what we expect
1041
self.assertEqual(e.errno, errno.EACCES)
1042
if os.name == "posix":
1043
self.assertEndsWith(e.to_path, "/first-dir/newname")
1045
self.assertEqual(os.path.basename(e.from_path), "myfile")
1047
def test_set_executability_order(self):
1048
"""Ensure that executability behaves the same, no matter what order.
1050
- create file and set executability simultaneously
1051
- create file and set executability afterward
1052
- unsetting the executability of a file whose executability has not been
1053
declared should throw an exception (this may happen when a
1054
merge attempts to create a file with a duplicate ID)
1056
transform, root = self.get_transform()
1057
wt = transform._tree
1059
self.addCleanup(wt.unlock)
1060
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
1062
sac = transform.new_file('set_after_creation', root,
1063
'Set after creation', 'sac')
1064
transform.set_executability(True, sac)
1065
uws = transform.new_file('unset_without_set', root, 'Unset badly',
1067
self.assertRaises(KeyError, transform.set_executability, None, uws)
1069
self.assertTrue(wt.is_executable('set_on_creation'))
1070
self.assertTrue(wt.is_executable('set_after_creation'))
1072
def test_preserve_mode(self):
1073
"""File mode is preserved when replacing content"""
1074
if sys.platform == 'win32':
1075
raise TestSkipped('chmod has no effect on win32')
1076
transform, root = self.get_transform()
1077
transform.new_file('file1', root, 'contents', 'file1-id', True)
1079
self.wt.lock_write()
1080
self.addCleanup(self.wt.unlock)
1081
self.assertTrue(self.wt.is_executable('file1'))
1082
transform, root = self.get_transform()
1083
file1_id = transform.trans_id_tree_file_id('file1-id')
1084
transform.delete_contents(file1_id)
1085
transform.create_file('contents2', file1_id)
1087
self.assertTrue(self.wt.is_executable('file1'))
1089
def test__set_mode_stats_correctly(self):
1090
"""_set_mode stats to determine file mode."""
1091
if sys.platform == 'win32':
1092
raise TestSkipped('chmod has no effect on win32')
1096
def instrumented_stat(path):
1097
stat_paths.append(path)
1098
return real_stat(path)
1100
transform, root = self.get_transform()
1102
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
1103
file_id='bar-id-1', executable=False)
1106
transform, root = self.get_transform()
1107
bar1_id = transform.trans_id_tree_path('bar')
1108
bar2_id = transform.trans_id_tree_path('bar2')
1110
os.stat = instrumented_stat
1111
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
1114
transform.finalize()
1116
bar1_abspath = self.wt.abspath('bar')
1117
self.assertEqual([bar1_abspath], stat_paths)
1119
def test_iter_changes(self):
1120
self.wt.set_root_id('eert_toor')
1121
transform, root = self.get_transform()
1122
transform.new_file('old', root, 'blah', 'id-1', True)
1124
transform, root = self.get_transform()
1126
self.assertEqual([], list(transform.iter_changes()))
1127
old = transform.trans_id_tree_file_id('id-1')
1128
transform.unversion_file(old)
1129
self.assertEqual([('id-1', ('old', None), False, (True, False),
1130
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1131
(True, True))], list(transform.iter_changes()))
1132
transform.new_directory('new', root, 'id-1')
1133
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
1134
('eert_toor', 'eert_toor'), ('old', 'new'),
1135
('file', 'directory'),
1136
(True, False))], list(transform.iter_changes()))
1138
transform.finalize()
1140
def test_iter_changes_new(self):
1141
self.wt.set_root_id('eert_toor')
1142
transform, root = self.get_transform()
1143
transform.new_file('old', root, 'blah')
1145
transform, root = self.get_transform()
1147
old = transform.trans_id_tree_path('old')
1148
transform.version_file('id-1', old)
1149
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
1150
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1151
(False, False))], list(transform.iter_changes()))
1153
transform.finalize()
1155
def test_iter_changes_modifications(self):
1156
self.wt.set_root_id('eert_toor')
1157
transform, root = self.get_transform()
1158
transform.new_file('old', root, 'blah', 'id-1')
1159
transform.new_file('new', root, 'blah')
1160
transform.new_directory('subdir', root, 'subdir-id')
1162
transform, root = self.get_transform()
1164
old = transform.trans_id_tree_path('old')
1165
subdir = transform.trans_id_tree_file_id('subdir-id')
1166
new = transform.trans_id_tree_path('new')
1167
self.assertEqual([], list(transform.iter_changes()))
1170
transform.delete_contents(old)
1171
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1172
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1173
(False, False))], list(transform.iter_changes()))
1176
transform.create_file('blah', old)
1177
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1178
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1179
(False, False))], list(transform.iter_changes()))
1180
transform.cancel_deletion(old)
1181
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1182
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1183
(False, False))], list(transform.iter_changes()))
1184
transform.cancel_creation(old)
1186
# move file_id to a different file
1187
self.assertEqual([], list(transform.iter_changes()))
1188
transform.unversion_file(old)
1189
transform.version_file('id-1', new)
1190
transform.adjust_path('old', root, new)
1191
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1192
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1193
(False, False))], list(transform.iter_changes()))
1194
transform.cancel_versioning(new)
1195
transform._removed_id = set()
1198
self.assertEqual([], list(transform.iter_changes()))
1199
transform.set_executability(True, old)
1200
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1201
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1202
(False, True))], list(transform.iter_changes()))
1203
transform.set_executability(None, old)
1206
self.assertEqual([], list(transform.iter_changes()))
1207
transform.adjust_path('new', root, old)
1208
transform._new_parent = {}
1209
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1210
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1211
(False, False))], list(transform.iter_changes()))
1212
transform._new_name = {}
1215
self.assertEqual([], list(transform.iter_changes()))
1216
transform.adjust_path('new', subdir, old)
1217
transform._new_name = {}
1218
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1219
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1220
('file', 'file'), (False, False))],
1221
list(transform.iter_changes()))
1222
transform._new_path = {}
1225
transform.finalize()
1227
def test_iter_changes_modified_bleed(self):
1228
self.wt.set_root_id('eert_toor')
1229
"""Modified flag should not bleed from one change to another"""
1230
# unfortunately, we have no guarantee that file1 (which is modified)
1231
# will be applied before file2. And if it's applied after file2, it
1232
# obviously can't bleed into file2's change output. But for now, it
1234
transform, root = self.get_transform()
1235
transform.new_file('file1', root, 'blah', 'id-1')
1236
transform.new_file('file2', root, 'blah', 'id-2')
1238
transform, root = self.get_transform()
1240
transform.delete_contents(transform.trans_id_file_id('id-1'))
1241
transform.set_executability(True,
1242
transform.trans_id_file_id('id-2'))
1243
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1244
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1245
('file', None), (False, False)),
1246
('id-2', (u'file2', u'file2'), False, (True, True),
1247
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1248
('file', 'file'), (False, True))],
1249
list(transform.iter_changes()))
1251
transform.finalize()
1253
def test_iter_changes_move_missing(self):
1254
"""Test moving ids with no files around"""
1255
self.wt.set_root_id('toor_eert')
1256
# Need two steps because versioning a non-existant file is a conflict.
1257
transform, root = self.get_transform()
1258
transform.new_directory('floater', root, 'floater-id')
1260
transform, root = self.get_transform()
1261
transform.delete_contents(transform.trans_id_tree_path('floater'))
1263
transform, root = self.get_transform()
1264
floater = transform.trans_id_tree_path('floater')
1266
transform.adjust_path('flitter', root, floater)
1267
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1268
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1269
(None, None), (False, False))], list(transform.iter_changes()))
1271
transform.finalize()
1273
def test_iter_changes_pointless(self):
1274
"""Ensure that no-ops are not treated as modifications"""
1275
self.wt.set_root_id('eert_toor')
1276
transform, root = self.get_transform()
1277
transform.new_file('old', root, 'blah', 'id-1')
1278
transform.new_directory('subdir', root, 'subdir-id')
1280
transform, root = self.get_transform()
1282
old = transform.trans_id_tree_path('old')
1283
subdir = transform.trans_id_tree_file_id('subdir-id')
1284
self.assertEqual([], list(transform.iter_changes()))
1285
transform.delete_contents(subdir)
1286
transform.create_directory(subdir)
1287
transform.set_executability(False, old)
1288
transform.unversion_file(old)
1289
transform.version_file('id-1', old)
1290
transform.adjust_path('old', root, old)
1291
self.assertEqual([], list(transform.iter_changes()))
1293
transform.finalize()
1295
def test_rename_count(self):
1296
transform, root = self.get_transform()
1297
transform.new_file('name1', root, 'contents')
1298
self.assertEqual(transform.rename_count, 0)
1300
self.assertEqual(transform.rename_count, 1)
1301
transform2, root = self.get_transform()
1302
transform2.adjust_path('name2', root,
1303
transform2.trans_id_tree_path('name1'))
1304
self.assertEqual(transform2.rename_count, 0)
1306
self.assertEqual(transform2.rename_count, 2)
1308
def test_change_parent(self):
1309
"""Ensure that after we change a parent, the results are still right.
1311
Renames and parent changes on pending transforms can happen as part
1312
of conflict resolution, and are explicitly permitted by the
1315
This test ensures they work correctly with the rename-avoidance
1318
transform, root = self.get_transform()
1319
parent1 = transform.new_directory('parent1', root)
1320
child1 = transform.new_file('child1', parent1, 'contents')
1321
parent2 = transform.new_directory('parent2', root)
1322
transform.adjust_path('child1', parent2, child1)
1324
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1325
self.assertPathExists(self.wt.abspath('parent2/child1'))
1326
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1327
# no rename for child1 (counting only renames during apply)
1328
self.assertEqual(2, transform.rename_count)
1330
def test_cancel_parent(self):
1331
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1333
This is like the test_change_parent, except that we cancel the parent
1334
before adjusting the path. The transform must detect that the
1335
directory is non-empty, and move children to safe locations.
1337
transform, root = self.get_transform()
1338
parent1 = transform.new_directory('parent1', root)
1339
child1 = transform.new_file('child1', parent1, 'contents')
1340
child2 = transform.new_file('child2', parent1, 'contents')
1342
transform.cancel_creation(parent1)
1344
self.fail('Failed to move child1 before deleting parent1')
1345
transform.cancel_creation(child2)
1346
transform.create_directory(parent1)
1348
transform.cancel_creation(parent1)
1349
# If the transform incorrectly believes that child2 is still in
1350
# parent1's limbo directory, it will try to rename it and fail
1351
# because was already moved by the first cancel_creation.
1353
self.fail('Transform still thinks child2 is a child of parent1')
1354
parent2 = transform.new_directory('parent2', root)
1355
transform.adjust_path('child1', parent2, child1)
1357
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1358
self.assertPathExists(self.wt.abspath('parent2/child1'))
1359
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1360
self.assertEqual(2, transform.rename_count)
1362
def test_adjust_and_cancel(self):
1363
"""Make sure adjust_path keeps track of limbo children properly"""
1364
transform, root = self.get_transform()
1365
parent1 = transform.new_directory('parent1', root)
1366
child1 = transform.new_file('child1', parent1, 'contents')
1367
parent2 = transform.new_directory('parent2', root)
1368
transform.adjust_path('child1', parent2, child1)
1369
transform.cancel_creation(child1)
1371
transform.cancel_creation(parent1)
1372
# if the transform thinks child1 is still in parent1's limbo
1373
# directory, it will attempt to move it and fail.
1375
self.fail('Transform still thinks child1 is a child of parent1')
1376
transform.finalize()
1378
def test_noname_contents(self):
1379
"""TreeTransform should permit deferring naming files."""
1380
transform, root = self.get_transform()
1381
parent = transform.trans_id_file_id('parent-id')
1383
transform.create_directory(parent)
1385
self.fail("Can't handle contents with no name")
1386
transform.finalize()
1388
def test_noname_contents_nested(self):
1389
"""TreeTransform should permit deferring naming files."""
1390
transform, root = self.get_transform()
1391
parent = transform.trans_id_file_id('parent-id')
1393
transform.create_directory(parent)
1395
self.fail("Can't handle contents with no name")
1396
child = transform.new_directory('child', parent)
1397
transform.adjust_path('parent', root, parent)
1399
self.assertPathExists(self.wt.abspath('parent/child'))
1400
self.assertEqual(1, transform.rename_count)
1402
def test_reuse_name(self):
1403
"""Avoid reusing the same limbo name for different files"""
1404
transform, root = self.get_transform()
1405
parent = transform.new_directory('parent', root)
1406
child1 = transform.new_directory('child', parent)
1408
child2 = transform.new_directory('child', parent)
1410
self.fail('Tranform tried to use the same limbo name twice')
1411
transform.adjust_path('child2', parent, child2)
1413
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1414
# child2 is put into top-level limbo because child1 has already
1415
# claimed the direct limbo path when child2 is created. There is no
1416
# advantage in renaming files once they're in top-level limbo, except
1418
self.assertEqual(2, transform.rename_count)
1420
def test_reuse_when_first_moved(self):
1421
"""Don't avoid direct paths when it is safe to use them"""
1422
transform, root = self.get_transform()
1423
parent = transform.new_directory('parent', root)
1424
child1 = transform.new_directory('child', parent)
1425
transform.adjust_path('child1', parent, child1)
1426
child2 = transform.new_directory('child', parent)
1428
# limbo/new-1 => parent
1429
self.assertEqual(1, transform.rename_count)
1431
def test_reuse_after_cancel(self):
1432
"""Don't avoid direct paths when it is safe to use them"""
1433
transform, root = self.get_transform()
1434
parent2 = transform.new_directory('parent2', root)
1435
child1 = transform.new_directory('child1', parent2)
1436
transform.cancel_creation(parent2)
1437
transform.create_directory(parent2)
1438
child2 = transform.new_directory('child1', parent2)
1439
transform.adjust_path('child2', parent2, child1)
1441
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1442
self.assertEqual(2, transform.rename_count)
1444
def test_finalize_order(self):
1445
"""Finalize must be done in child-to-parent order"""
1446
transform, root = self.get_transform()
1447
parent = transform.new_directory('parent', root)
1448
child = transform.new_directory('child', parent)
1450
transform.finalize()
1452
self.fail('Tried to remove parent before child1')
1454
def test_cancel_with_cancelled_child_should_succeed(self):
1455
transform, root = self.get_transform()
1456
parent = transform.new_directory('parent', root)
1457
child = transform.new_directory('child', parent)
1458
transform.cancel_creation(child)
1459
transform.cancel_creation(parent)
1460
transform.finalize()
1462
def test_rollback_on_directory_clash(self):
1464
wt = self.make_branch_and_tree('.')
1465
tt = TreeTransform(wt) # TreeTransform obtains write lock
1467
foo = tt.new_directory('foo', tt.root)
1468
tt.new_file('bar', foo, 'foobar')
1469
baz = tt.new_directory('baz', tt.root)
1470
tt.new_file('qux', baz, 'quux')
1471
# Ask for a rename 'foo' -> 'baz'
1472
tt.adjust_path('baz', tt.root, foo)
1473
# Lie to tt that we've already resolved all conflicts.
1474
tt.apply(no_conflicts=True)
1478
# The rename will fail because the target directory is not empty (but
1479
# raises FileExists anyway).
1480
err = self.assertRaises(errors.FileExists, tt_helper)
1481
self.assertEndsWith(err.path, "/baz")
1483
def test_two_directories_clash(self):
1485
wt = self.make_branch_and_tree('.')
1486
tt = TreeTransform(wt) # TreeTransform obtains write lock
1488
foo_1 = tt.new_directory('foo', tt.root)
1489
tt.new_directory('bar', foo_1)
1490
# Adding the same directory with a different content
1491
foo_2 = tt.new_directory('foo', tt.root)
1492
tt.new_directory('baz', foo_2)
1493
# Lie to tt that we've already resolved all conflicts.
1494
tt.apply(no_conflicts=True)
1498
err = self.assertRaises(errors.FileExists, tt_helper)
1499
self.assertEndsWith(err.path, "/foo")
1501
def test_two_directories_clash_finalize(self):
1503
wt = self.make_branch_and_tree('.')
1504
tt = TreeTransform(wt) # TreeTransform obtains write lock
1506
foo_1 = tt.new_directory('foo', tt.root)
1507
tt.new_directory('bar', foo_1)
1508
# Adding the same directory with a different content
1509
foo_2 = tt.new_directory('foo', tt.root)
1510
tt.new_directory('baz', foo_2)
1511
# Lie to tt that we've already resolved all conflicts.
1512
tt.apply(no_conflicts=True)
1516
err = self.assertRaises(errors.FileExists, tt_helper)
1517
self.assertEndsWith(err.path, "/foo")
1519
def test_file_to_directory(self):
1520
wt = self.make_branch_and_tree('.')
1521
self.build_tree(['foo'])
1524
tt = TreeTransform(wt)
1525
self.addCleanup(tt.finalize)
1526
foo_trans_id = tt.trans_id_tree_path("foo")
1527
tt.delete_contents(foo_trans_id)
1528
tt.create_directory(foo_trans_id)
1529
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1530
tt.create_file(["aa\n"], bar_trans_id)
1531
tt.version_file("bar-1", bar_trans_id)
1533
self.assertPathExists("foo/bar")
1536
self.assertEqual(wt.kind("foo"), "directory")
1540
changes = wt.changes_from(wt.basis_tree())
1541
self.assertFalse(changes.has_changed(), changes)
1543
def test_file_to_symlink(self):
1544
self.requireFeature(SymlinkFeature)
1545
wt = self.make_branch_and_tree('.')
1546
self.build_tree(['foo'])
1549
tt = TreeTransform(wt)
1550
self.addCleanup(tt.finalize)
1551
foo_trans_id = tt.trans_id_tree_path("foo")
1552
tt.delete_contents(foo_trans_id)
1553
tt.create_symlink("bar", foo_trans_id)
1555
self.assertPathExists("foo")
1557
self.addCleanup(wt.unlock)
1558
self.assertEqual(wt.kind("foo"), "symlink")
1560
def test_dir_to_file(self):
1561
wt = self.make_branch_and_tree('.')
1562
self.build_tree(['foo/', 'foo/bar'])
1563
wt.add(['foo', 'foo/bar'])
1565
tt = TreeTransform(wt)
1566
self.addCleanup(tt.finalize)
1567
foo_trans_id = tt.trans_id_tree_path("foo")
1568
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1569
tt.delete_contents(foo_trans_id)
1570
tt.delete_versioned(bar_trans_id)
1571
tt.create_file(["aa\n"], foo_trans_id)
1573
self.assertPathExists("foo")
1575
self.addCleanup(wt.unlock)
1576
self.assertEqual(wt.kind("foo"), "file")
1578
def test_dir_to_hardlink(self):
1579
self.requireFeature(HardlinkFeature)
1580
wt = self.make_branch_and_tree('.')
1581
self.build_tree(['foo/', 'foo/bar'])
1582
wt.add(['foo', 'foo/bar'])
1584
tt = TreeTransform(wt)
1585
self.addCleanup(tt.finalize)
1586
foo_trans_id = tt.trans_id_tree_path("foo")
1587
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1588
tt.delete_contents(foo_trans_id)
1589
tt.delete_versioned(bar_trans_id)
1590
self.build_tree(['baz'])
1591
tt.create_hardlink("baz", foo_trans_id)
1593
self.assertPathExists("foo")
1594
self.assertPathExists("baz")
1596
self.addCleanup(wt.unlock)
1597
self.assertEqual(wt.kind("foo"), "file")
1599
def test_no_final_path(self):
1600
transform, root = self.get_transform()
1601
trans_id = transform.trans_id_file_id('foo')
1602
transform.create_file('bar', trans_id)
1603
transform.cancel_creation(trans_id)
1606
def test_create_from_tree(self):
1607
tree1 = self.make_branch_and_tree('tree1')
1608
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1609
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1610
tree2 = self.make_branch_and_tree('tree2')
1611
tt = TreeTransform(tree2)
1612
foo_trans_id = tt.create_path('foo', tt.root)
1613
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id='foo-id')
1614
bar_trans_id = tt.create_path('bar', tt.root)
1615
create_from_tree(tt, bar_trans_id, tree1, 'bar', file_id='bar-id')
1617
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1618
self.assertFileEqual('baz', 'tree2/bar')
1620
def test_create_from_tree_bytes(self):
1621
"""Provided lines are used instead of tree content."""
1622
tree1 = self.make_branch_and_tree('tree1')
1623
self.build_tree_contents([('tree1/foo', 'bar'),])
1624
tree1.add('foo', 'foo-id')
1625
tree2 = self.make_branch_and_tree('tree2')
1626
tt = TreeTransform(tree2)
1627
foo_trans_id = tt.create_path('foo', tt.root)
1628
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id='foo-id',
1631
self.assertFileEqual('qux', 'tree2/foo')
1633
def test_create_from_tree_symlink(self):
1634
self.requireFeature(SymlinkFeature)
1635
tree1 = self.make_branch_and_tree('tree1')
1636
os.symlink('bar', 'tree1/foo')
1637
tree1.add('foo', 'foo-id')
1638
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1639
foo_trans_id = tt.create_path('foo', tt.root)
1640
create_from_tree(tt, foo_trans_id, tree1, 'foo', file_id='foo-id')
1642
self.assertEqual('bar', os.readlink('tree2/foo'))
494
1645
class TransformGroup(object):
495
def __init__(self, dirname):
1647
def __init__(self, dirname, root_id):
496
1648
self.name = dirname
497
1649
os.mkdir(dirname)
498
self.wt = BzrDir.create_standalone_workingtree(dirname)
1650
self.wt = ControlDir.create_standalone_workingtree(dirname)
1651
self.wt.set_root_id(root_id)
499
1652
self.b = self.wt.branch
500
1653
self.tt = TreeTransform(self.wt)
501
1654
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
503
1657
def conflict_text(tree, merge):
504
1658
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
505
1659
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1662
class TestInventoryAltered(tests.TestCaseWithTransport):
1664
def test_inventory_altered_unchanged(self):
1665
tree = self.make_branch_and_tree('tree')
1666
self.build_tree(['tree/foo'])
1667
tree.add('foo', 'foo-id')
1668
with TransformPreview(tree) as tt:
1669
self.assertEqual([], tt._inventory_altered())
1671
def test_inventory_altered_changed_parent_id(self):
1672
tree = self.make_branch_and_tree('tree')
1673
self.build_tree(['tree/foo'])
1674
tree.add('foo', 'foo-id')
1675
with TransformPreview(tree) as tt:
1676
tt.unversion_file(tt.root)
1677
tt.version_file('new-id', tt.root)
1678
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1679
foo_tuple = ('foo', foo_trans_id)
1680
root_tuple = ('', tt.root)
1681
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1683
def test_inventory_altered_noop_changed_parent_id(self):
1684
tree = self.make_branch_and_tree('tree')
1685
self.build_tree(['tree/foo'])
1686
tree.add('foo', 'foo-id')
1687
with TransformPreview(tree) as tt:
1688
tt.unversion_file(tt.root)
1689
tt.version_file(tree.get_root_id(), tt.root)
1690
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1691
self.assertEqual([], tt._inventory_altered())
508
1694
class TestTransformMerge(TestCaseInTempDir):
509
1696
def test_text_merge(self):
510
base = TransformGroup("base")
1697
root_id = generate_ids.gen_root_id()
1698
base = TransformGroup("base", root_id)
511
1699
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
512
1700
base.tt.new_file('b', base.root, 'b1', 'b')
513
1701
base.tt.new_file('c', base.root, 'c', 'c')
686
1877
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
687
1878
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
689
class TestBuildTree(TestCaseInTempDir):
690
def test_build_tree(self):
691
if not has_symlinks():
692
raise TestSkipped('Test requires symlink support')
1881
class TestBuildTree(tests.TestCaseWithTransport):
1883
def test_build_tree_with_symlinks(self):
1884
self.requireFeature(SymlinkFeature)
694
a = BzrDir.create_standalone_workingtree('a')
1886
a = ControlDir.create_standalone_workingtree('a')
695
1887
os.mkdir('a/foo')
696
file('a/foo/bar', 'wb').write('contents')
1888
with file('a/foo/bar', 'wb') as f: f.write('contents')
697
1889
os.symlink('a/foo/bar', 'a/foo/baz')
698
1890
a.add(['foo', 'foo/bar', 'foo/baz'])
699
1891
a.commit('initial commit')
700
b = BzrDir.create_standalone_workingtree('b')
701
build_tree(a.basis_tree(), b)
1892
b = ControlDir.create_standalone_workingtree('b')
1893
basis = a.basis_tree()
1895
self.addCleanup(basis.unlock)
1896
build_tree(basis, b)
702
1897
self.assertIs(os.path.isdir('b/foo'), True)
703
1898
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
704
1899
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):
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~')
1901
def test_build_with_references(self):
1902
tree = self.make_branch_and_tree('source',
1903
format='development-subtree')
1904
subtree = self.make_branch_and_tree('source/subtree',
1905
format='development-subtree')
1906
tree.add_reference(subtree)
1907
tree.commit('a revision')
1908
tree.branch.create_checkout('target')
1909
self.assertPathExists('target')
1910
self.assertPathExists('target/subtree')
1912
def test_file_conflict_handling(self):
1913
"""Ensure that when building trees, conflict handling is done"""
1914
source = self.make_branch_and_tree('source')
1915
target = self.make_branch_and_tree('target')
1916
self.build_tree(['source/file', 'target/file'])
1917
source.add('file', 'new-file')
1918
source.commit('added file')
1919
build_tree(source.basis_tree(), target)
1920
self.assertEqual([DuplicateEntry('Moved existing file to',
1921
'file.moved', 'file', None, 'new-file')],
1923
target2 = self.make_branch_and_tree('target2')
1924
target_file = file('target2/file', 'wb')
1926
source_file = file('source/file', 'rb')
1928
target_file.write(source_file.read())
1933
build_tree(source.basis_tree(), target2)
1934
self.assertEqual([], target2.conflicts())
1936
def test_symlink_conflict_handling(self):
1937
"""Ensure that when building trees, conflict handling is done"""
1938
self.requireFeature(SymlinkFeature)
1939
source = self.make_branch_and_tree('source')
1940
os.symlink('foo', 'source/symlink')
1941
source.add('symlink', 'new-symlink')
1942
source.commit('added file')
1943
target = self.make_branch_and_tree('target')
1944
os.symlink('bar', 'target/symlink')
1945
build_tree(source.basis_tree(), target)
1946
self.assertEqual([DuplicateEntry('Moved existing file to',
1947
'symlink.moved', 'symlink', None, 'new-symlink')],
1949
target = self.make_branch_and_tree('target2')
1950
os.symlink('foo', 'target2/symlink')
1951
build_tree(source.basis_tree(), target)
1952
self.assertEqual([], target.conflicts())
1954
def test_directory_conflict_handling(self):
1955
"""Ensure that when building trees, conflict handling is done"""
1956
source = self.make_branch_and_tree('source')
1957
target = self.make_branch_and_tree('target')
1958
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1959
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1960
source.commit('added file')
1961
build_tree(source.basis_tree(), target)
1962
self.assertEqual([], target.conflicts())
1963
self.assertPathExists('target/dir1/file')
1965
# Ensure contents are merged
1966
target = self.make_branch_and_tree('target2')
1967
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1968
build_tree(source.basis_tree(), target)
1969
self.assertEqual([], target.conflicts())
1970
self.assertPathExists('target2/dir1/file2')
1971
self.assertPathExists('target2/dir1/file')
1973
# Ensure new contents are suppressed for existing branches
1974
target = self.make_branch_and_tree('target3')
1975
self.make_branch('target3/dir1')
1976
self.build_tree(['target3/dir1/file2'])
1977
build_tree(source.basis_tree(), target)
1978
self.assertPathDoesNotExist('target3/dir1/file')
1979
self.assertPathExists('target3/dir1/file2')
1980
self.assertPathExists('target3/dir1.diverted/file')
1981
self.assertEqual([DuplicateEntry('Diverted to',
1982
'dir1.diverted', 'dir1', 'new-dir1', None)],
1985
target = self.make_branch_and_tree('target4')
1986
self.build_tree(['target4/dir1/'])
1987
self.make_branch('target4/dir1/file')
1988
build_tree(source.basis_tree(), target)
1989
self.assertPathExists('target4/dir1/file')
1990
self.assertEqual('directory', file_kind('target4/dir1/file'))
1991
self.assertPathExists('target4/dir1/file.diverted')
1992
self.assertEqual([DuplicateEntry('Diverted to',
1993
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1996
def test_mixed_conflict_handling(self):
1997
"""Ensure that when building trees, conflict handling is done"""
1998
source = self.make_branch_and_tree('source')
1999
target = self.make_branch_and_tree('target')
2000
self.build_tree(['source/name', 'target/name/'])
2001
source.add('name', 'new-name')
2002
source.commit('added file')
2003
build_tree(source.basis_tree(), target)
2004
self.assertEqual([DuplicateEntry('Moved existing file to',
2005
'name.moved', 'name', None, 'new-name')], target.conflicts())
2007
def test_raises_in_populated(self):
2008
source = self.make_branch_and_tree('source')
2009
self.build_tree(['source/name'])
2011
source.commit('added name')
2012
target = self.make_branch_and_tree('target')
2013
self.build_tree(['target/name'])
2015
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2016
build_tree, source.basis_tree(), target)
2018
def test_build_tree_rename_count(self):
2019
source = self.make_branch_and_tree('source')
2020
self.build_tree(['source/file1', 'source/dir1/'])
2021
source.add(['file1', 'dir1'])
2022
source.commit('add1')
2023
target1 = self.make_branch_and_tree('target1')
2024
transform_result = build_tree(source.basis_tree(), target1)
2025
self.assertEqual(2, transform_result.rename_count)
2027
self.build_tree(['source/dir1/file2'])
2028
source.add(['dir1/file2'])
2029
source.commit('add3')
2030
target2 = self.make_branch_and_tree('target2')
2031
transform_result = build_tree(source.basis_tree(), target2)
2032
# children of non-root directories should not be renamed
2033
self.assertEqual(2, transform_result.rename_count)
2035
def create_ab_tree(self):
2036
"""Create a committed test tree with two files"""
2037
source = self.make_branch_and_tree('source')
2038
self.build_tree_contents([('source/file1', 'A')])
2039
self.build_tree_contents([('source/file2', 'B')])
2040
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2041
source.commit('commit files')
2043
self.addCleanup(source.unlock)
2046
def test_build_tree_accelerator_tree(self):
2047
source = self.create_ab_tree()
2048
self.build_tree_contents([('source/file2', 'C')])
2050
real_source_get_file = source.get_file
2051
def get_file(path, file_id=None):
2052
calls.append(file_id)
2053
return real_source_get_file(path, file_id)
2054
source.get_file = get_file
2055
target = self.make_branch_and_tree('target')
2056
revision_tree = source.basis_tree()
2057
revision_tree.lock_read()
2058
self.addCleanup(revision_tree.unlock)
2059
build_tree(revision_tree, target, source)
2060
self.assertEqual(['file1-id'], calls)
2062
self.addCleanup(target.unlock)
2063
self.assertEqual([], list(target.iter_changes(revision_tree)))
2065
def test_build_tree_accelerator_tree_observes_sha1(self):
2066
source = self.create_ab_tree()
2067
sha1 = osutils.sha_string('A')
2068
target = self.make_branch_and_tree('target')
2070
self.addCleanup(target.unlock)
2071
state = target.current_dirstate()
2072
state._cutoff_time = time.time() + 60
2073
build_tree(source.basis_tree(), target, source)
2074
entry = state._get_entry(0, path_utf8='file1')
2075
self.assertEqual(sha1, entry[1][0][1])
2077
def test_build_tree_accelerator_tree_missing_file(self):
2078
source = self.create_ab_tree()
2079
os.unlink('source/file1')
2080
source.remove(['file2'])
2081
target = self.make_branch_and_tree('target')
2082
revision_tree = source.basis_tree()
2083
revision_tree.lock_read()
2084
self.addCleanup(revision_tree.unlock)
2085
build_tree(revision_tree, target, source)
2087
self.addCleanup(target.unlock)
2088
self.assertEqual([], list(target.iter_changes(revision_tree)))
2090
def test_build_tree_accelerator_wrong_kind(self):
2091
self.requireFeature(SymlinkFeature)
2092
source = self.make_branch_and_tree('source')
2093
self.build_tree_contents([('source/file1', '')])
2094
self.build_tree_contents([('source/file2', '')])
2095
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2096
source.commit('commit files')
2097
os.unlink('source/file2')
2098
self.build_tree_contents([('source/file2/', 'C')])
2099
os.unlink('source/file1')
2100
os.symlink('file2', 'source/file1')
2102
real_source_get_file = source.get_file
2103
def get_file(path, file_id=None):
2104
calls.append(file_id)
2105
return real_source_get_file(path, file_id)
2106
source.get_file = get_file
2107
target = self.make_branch_and_tree('target')
2108
revision_tree = source.basis_tree()
2109
revision_tree.lock_read()
2110
self.addCleanup(revision_tree.unlock)
2111
build_tree(revision_tree, target, source)
2112
self.assertEqual([], calls)
2114
self.addCleanup(target.unlock)
2115
self.assertEqual([], list(target.iter_changes(revision_tree)))
2117
def test_build_tree_hardlink(self):
2118
self.requireFeature(HardlinkFeature)
2119
source = self.create_ab_tree()
2120
target = self.make_branch_and_tree('target')
2121
revision_tree = source.basis_tree()
2122
revision_tree.lock_read()
2123
self.addCleanup(revision_tree.unlock)
2124
build_tree(revision_tree, target, source, hardlink=True)
2126
self.addCleanup(target.unlock)
2127
self.assertEqual([], list(target.iter_changes(revision_tree)))
2128
source_stat = os.stat('source/file1')
2129
target_stat = os.stat('target/file1')
2130
self.assertEqual(source_stat, target_stat)
2132
# Explicitly disallowing hardlinks should prevent them.
2133
target2 = self.make_branch_and_tree('target2')
2134
build_tree(revision_tree, target2, source, hardlink=False)
2136
self.addCleanup(target2.unlock)
2137
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2138
source_stat = os.stat('source/file1')
2139
target2_stat = os.stat('target2/file1')
2140
self.assertNotEqual(source_stat, target2_stat)
2142
def test_build_tree_accelerator_tree_moved(self):
2143
source = self.make_branch_and_tree('source')
2144
self.build_tree_contents([('source/file1', 'A')])
2145
source.add(['file1'], ['file1-id'])
2146
source.commit('commit files')
2147
source.rename_one('file1', 'file2')
2149
self.addCleanup(source.unlock)
2150
target = self.make_branch_and_tree('target')
2151
revision_tree = source.basis_tree()
2152
revision_tree.lock_read()
2153
self.addCleanup(revision_tree.unlock)
2154
build_tree(revision_tree, target, source)
2156
self.addCleanup(target.unlock)
2157
self.assertEqual([], list(target.iter_changes(revision_tree)))
2159
def test_build_tree_hardlinks_preserve_execute(self):
2160
self.requireFeature(HardlinkFeature)
2161
source = self.create_ab_tree()
2162
tt = TreeTransform(source)
2163
trans_id = tt.trans_id_tree_file_id('file1-id')
2164
tt.set_executability(True, trans_id)
2166
self.assertTrue(source.is_executable('file1'))
2167
target = self.make_branch_and_tree('target')
2168
revision_tree = source.basis_tree()
2169
revision_tree.lock_read()
2170
self.addCleanup(revision_tree.unlock)
2171
build_tree(revision_tree, target, source, hardlink=True)
2173
self.addCleanup(target.unlock)
2174
self.assertEqual([], list(target.iter_changes(revision_tree)))
2175
self.assertTrue(source.is_executable('file1'))
2177
def install_rot13_content_filter(self, pattern):
2179
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2180
# below, but that looks a bit... hard to read even if it's exactly
2182
original_registry = filters._reset_registry()
2183
def restore_registry():
2184
filters._reset_registry(original_registry)
2185
self.addCleanup(restore_registry)
2186
def rot13(chunks, context=None):
2187
return [''.join(chunks).encode('rot13')]
2188
rot13filter = filters.ContentFilter(rot13, rot13)
2189
filters.filter_stacks_registry.register(
2190
'rot13', {'yes': [rot13filter]}.get)
2191
os.mkdir(self.test_home_dir + '/.bazaar')
2192
rules_filename = self.test_home_dir + '/.bazaar/rules'
2193
f = open(rules_filename, 'wb')
2194
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2196
def uninstall_rules():
2197
os.remove(rules_filename)
2199
self.addCleanup(uninstall_rules)
2202
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2203
"""build_tree will not hardlink files that have content filtering rules
2204
applied to them (but will still hardlink other files from the same tree
2207
self.requireFeature(HardlinkFeature)
2208
self.install_rot13_content_filter('file1')
2209
source = self.create_ab_tree()
2210
target = self.make_branch_and_tree('target')
2211
revision_tree = source.basis_tree()
2212
revision_tree.lock_read()
2213
self.addCleanup(revision_tree.unlock)
2214
build_tree(revision_tree, target, source, hardlink=True)
2216
self.addCleanup(target.unlock)
2217
self.assertEqual([], list(target.iter_changes(revision_tree)))
2218
source_stat = os.stat('source/file1')
2219
target_stat = os.stat('target/file1')
2220
self.assertNotEqual(source_stat, target_stat)
2221
source_stat = os.stat('source/file2')
2222
target_stat = os.stat('target/file2')
2223
self.assertEqualStat(source_stat, target_stat)
2225
def test_case_insensitive_build_tree_inventory(self):
2226
if (features.CaseInsensitiveFilesystemFeature.available()
2227
or features.CaseInsCasePresFilenameFeature.available()):
2228
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2229
source = self.make_branch_and_tree('source')
2230
self.build_tree(['source/file', 'source/FILE'])
2231
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2232
source.commit('added files')
2233
# Don't try this at home, kids!
2234
# Force the tree to report that it is case insensitive
2235
target = self.make_branch_and_tree('target')
2236
target.case_sensitive = False
2237
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2238
self.assertEqual('file.moved', target.id2path('lower-id'))
2239
self.assertEqual('FILE', target.id2path('upper-id'))
2241
def test_build_tree_observes_sha(self):
2242
source = self.make_branch_and_tree('source')
2243
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2244
source.add(['file1', 'dir', 'dir/file2'],
2245
['file1-id', 'dir-id', 'file2-id'])
2246
source.commit('new files')
2247
target = self.make_branch_and_tree('target')
2249
self.addCleanup(target.unlock)
2250
# We make use of the fact that DirState caches its cutoff time. So we
2251
# set the 'safe' time to one minute in the future.
2252
state = target.current_dirstate()
2253
state._cutoff_time = time.time() + 60
2254
build_tree(source.basis_tree(), target)
2255
entry1_sha = osutils.sha_file_by_name('source/file1')
2256
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2257
# entry[1] is the state information, entry[1][0] is the state of the
2258
# working tree, entry[1][0][1] is the sha value for the current working
2260
entry1 = state._get_entry(0, path_utf8='file1')
2261
self.assertEqual(entry1_sha, entry1[1][0][1])
2262
# The 'size' field must also be set.
2263
self.assertEqual(25, entry1[1][0][2])
2264
entry1_state = entry1[1][0]
2265
entry2 = state._get_entry(0, path_utf8='dir/file2')
2266
self.assertEqual(entry2_sha, entry2[1][0][1])
2267
self.assertEqual(29, entry2[1][0][2])
2268
entry2_state = entry2[1][0]
2269
# Now, make sure that we don't have to re-read the content. The
2270
# packed_stat should match exactly.
2271
self.assertEqual(entry1_sha, target.get_file_sha1('file1', 'file1-id'))
2272
self.assertEqual(entry2_sha,
2273
target.get_file_sha1('dir/file2', 'file2-id'))
2274
self.assertEqual(entry1_state, entry1[1][0])
2275
self.assertEqual(entry2_state, entry2[1][0])
2278
class TestCommitTransform(tests.TestCaseWithTransport):
2280
def get_branch(self):
2281
tree = self.make_branch_and_tree('tree')
2283
self.addCleanup(tree.unlock)
2284
tree.commit('empty commit')
2287
def get_branch_and_transform(self):
2288
branch = self.get_branch()
2289
tt = TransformPreview(branch.basis_tree())
2290
self.addCleanup(tt.finalize)
2293
def test_commit_wrong_basis(self):
2294
branch = self.get_branch()
2295
basis = branch.repository.revision_tree(
2296
_mod_revision.NULL_REVISION)
2297
tt = TransformPreview(basis)
2298
self.addCleanup(tt.finalize)
2299
e = self.assertRaises(ValueError, tt.commit, branch, '')
2300
self.assertEqual('TreeTransform not based on branch basis: null:',
2303
def test_empy_commit(self):
2304
branch, tt = self.get_branch_and_transform()
2305
rev = tt.commit(branch, 'my message')
2306
self.assertEqual(2, branch.revno())
2307
repo = branch.repository
2308
self.assertEqual('my message', repo.get_revision(rev).message)
2310
def test_merge_parents(self):
2311
branch, tt = self.get_branch_and_transform()
2312
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2313
self.assertEqual(['rev1b', 'rev1c'],
2314
branch.basis_tree().get_parent_ids()[1:])
2316
def test_first_commit(self):
2317
branch = self.make_branch('branch')
2319
self.addCleanup(branch.unlock)
2320
tt = TransformPreview(branch.basis_tree())
2321
self.addCleanup(tt.finalize)
2322
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2323
rev = tt.commit(branch, 'my message')
2324
self.assertEqual([], branch.basis_tree().get_parent_ids())
2325
self.assertNotEqual(_mod_revision.NULL_REVISION,
2326
branch.last_revision())
2328
def test_first_commit_with_merge_parents(self):
2329
branch = self.make_branch('branch')
2331
self.addCleanup(branch.unlock)
2332
tt = TransformPreview(branch.basis_tree())
2333
self.addCleanup(tt.finalize)
2334
e = self.assertRaises(ValueError, tt.commit, branch,
2335
'my message', ['rev1b-id'])
2336
self.assertEqual('Cannot supply merge parents for first commit.',
2338
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2340
def test_add_files(self):
2341
branch, tt = self.get_branch_and_transform()
2342
tt.new_file('file', tt.root, 'contents', 'file-id')
2343
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2344
if SymlinkFeature.available():
2345
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2346
rev = tt.commit(branch, 'message')
2347
tree = branch.basis_tree()
2348
self.assertEqual('file', tree.id2path('file-id'))
2349
self.assertEqual('contents', tree.get_file_text('file', 'file-id'))
2350
self.assertEqual('dir', tree.id2path('dir-id'))
2351
if SymlinkFeature.available():
2352
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2353
self.assertEqual('target', tree.get_symlink_target('dir/symlink'))
2355
def test_add_unversioned(self):
2356
branch, tt = self.get_branch_and_transform()
2357
tt.new_file('file', tt.root, 'contents')
2358
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2359
'message', strict=True)
2361
def test_modify_strict(self):
2362
branch, tt = self.get_branch_and_transform()
2363
tt.new_file('file', tt.root, 'contents', 'file-id')
2364
tt.commit(branch, 'message', strict=True)
2365
tt = TransformPreview(branch.basis_tree())
2366
self.addCleanup(tt.finalize)
2367
trans_id = tt.trans_id_file_id('file-id')
2368
tt.delete_contents(trans_id)
2369
tt.create_file('contents', trans_id)
2370
tt.commit(branch, 'message', strict=True)
2372
def test_commit_malformed(self):
2373
"""Committing a malformed transform should raise an exception.
2375
In this case, we are adding a file without adding its parent.
2377
branch, tt = self.get_branch_and_transform()
2378
parent_id = tt.trans_id_file_id('parent-id')
2379
tt.new_file('file', parent_id, 'contents', 'file-id')
2380
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2383
def test_commit_rich_revision_data(self):
2384
branch, tt = self.get_branch_and_transform()
2385
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2386
committer='me <me@example.com>',
2387
revprops={'foo': 'bar'}, revision_id='revid-1',
2388
authors=['Author1 <author1@example.com>',
2389
'Author2 <author2@example.com>',
2391
self.assertEqual('revid-1', rev_id)
2392
revision = branch.repository.get_revision(rev_id)
2393
self.assertEqual(1, revision.timestamp)
2394
self.assertEqual(43201, revision.timezone)
2395
self.assertEqual('me <me@example.com>', revision.committer)
2396
self.assertEqual(['Author1 <author1@example.com>',
2397
'Author2 <author2@example.com>'],
2398
revision.get_apparent_authors())
2399
del revision.properties['authors']
2400
self.assertEqual({'foo': 'bar',
2401
'branch-nick': 'tree'},
2402
revision.properties)
2404
def test_no_explicit_revprops(self):
2405
branch, tt = self.get_branch_and_transform()
2406
rev_id = tt.commit(branch, 'message', authors=[
2407
'Author1 <author1@example.com>',
2408
'Author2 <author2@example.com>', ])
2409
revision = branch.repository.get_revision(rev_id)
2410
self.assertEqual(['Author1 <author1@example.com>',
2411
'Author2 <author2@example.com>'],
2412
revision.get_apparent_authors())
2413
self.assertEqual('tree', revision.properties['branch-nick'])
2416
class TestFileMover(tests.TestCaseWithTransport):
2418
def test_file_mover(self):
2419
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2420
mover = _FileMover()
2421
mover.rename('a', 'q')
2422
self.assertPathExists('q')
2423
self.assertPathDoesNotExist('a')
2424
self.assertPathExists('q/b')
2425
self.assertPathExists('c')
2426
self.assertPathExists('c/d')
2428
def test_pre_delete_rollback(self):
2429
self.build_tree(['a/'])
2430
mover = _FileMover()
2431
mover.pre_delete('a', 'q')
2432
self.assertPathExists('q')
2433
self.assertPathDoesNotExist('a')
2435
self.assertPathDoesNotExist('q')
2436
self.assertPathExists('a')
2438
def test_apply_deletions(self):
2439
self.build_tree(['a/', 'b/'])
2440
mover = _FileMover()
2441
mover.pre_delete('a', 'q')
2442
mover.pre_delete('b', 'r')
2443
self.assertPathExists('q')
2444
self.assertPathExists('r')
2445
self.assertPathDoesNotExist('a')
2446
self.assertPathDoesNotExist('b')
2447
mover.apply_deletions()
2448
self.assertPathDoesNotExist('q')
2449
self.assertPathDoesNotExist('r')
2450
self.assertPathDoesNotExist('a')
2451
self.assertPathDoesNotExist('b')
2453
def test_file_mover_rollback(self):
2454
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2455
mover = _FileMover()
2456
mover.rename('c/d', 'c/f')
2457
mover.rename('c/e', 'c/d')
2459
mover.rename('a', 'c')
2460
except errors.FileExists as e:
2462
self.assertPathExists('a')
2463
self.assertPathExists('c/d')
2466
class Bogus(Exception):
2470
class TestTransformRollback(tests.TestCaseWithTransport):
2472
class ExceptionFileMover(_FileMover):
2474
def __init__(self, bad_source=None, bad_target=None):
2475
_FileMover.__init__(self)
2476
self.bad_source = bad_source
2477
self.bad_target = bad_target
2479
def rename(self, source, target):
2480
if (self.bad_source is not None and
2481
source.endswith(self.bad_source)):
2483
elif (self.bad_target is not None and
2484
target.endswith(self.bad_target)):
2487
_FileMover.rename(self, source, target)
2489
def test_rollback_rename(self):
2490
tree = self.make_branch_and_tree('.')
2491
self.build_tree(['a/', 'a/b'])
2492
tt = TreeTransform(tree)
2493
self.addCleanup(tt.finalize)
2494
a_id = tt.trans_id_tree_path('a')
2495
tt.adjust_path('c', tt.root, a_id)
2496
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2497
self.assertRaises(Bogus, tt.apply,
2498
_mover=self.ExceptionFileMover(bad_source='a'))
2499
self.assertPathExists('a')
2500
self.assertPathExists('a/b')
2502
self.assertPathExists('c')
2503
self.assertPathExists('c/d')
2505
def test_rollback_rename_into_place(self):
2506
tree = self.make_branch_and_tree('.')
2507
self.build_tree(['a/', 'a/b'])
2508
tt = TreeTransform(tree)
2509
self.addCleanup(tt.finalize)
2510
a_id = tt.trans_id_tree_path('a')
2511
tt.adjust_path('c', tt.root, a_id)
2512
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2513
self.assertRaises(Bogus, tt.apply,
2514
_mover=self.ExceptionFileMover(bad_target='c/d'))
2515
self.assertPathExists('a')
2516
self.assertPathExists('a/b')
2518
self.assertPathExists('c')
2519
self.assertPathExists('c/d')
2521
def test_rollback_deletion(self):
2522
tree = self.make_branch_and_tree('.')
2523
self.build_tree(['a/', 'a/b'])
2524
tt = TreeTransform(tree)
2525
self.addCleanup(tt.finalize)
2526
a_id = tt.trans_id_tree_path('a')
2527
tt.delete_contents(a_id)
2528
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2529
self.assertRaises(Bogus, tt.apply,
2530
_mover=self.ExceptionFileMover(bad_target='d'))
2531
self.assertPathExists('a')
2532
self.assertPathExists('a/b')
2535
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2536
"""Ensure treetransform creation errors can be safely cleaned up after"""
2538
def _override_globals_in_method(self, instance, method_name, globals):
2539
"""Replace method on instance with one with updated globals"""
2541
func = getattr(instance, method_name).__func__
2542
new_globals = dict(func.__globals__)
2543
new_globals.update(globals)
2544
new_func = types.FunctionType(func.__code__, new_globals,
2545
func.__name__, func.__defaults__)
2546
setattr(instance, method_name,
2547
types.MethodType(new_func, instance, instance.__class__))
2548
self.addCleanup(delattr, instance, method_name)
2551
def _fake_open_raises_before(name, mode):
2552
"""Like open() but raises before doing anything"""
2556
def _fake_open_raises_after(name, mode):
2557
"""Like open() but raises after creating file without returning"""
2558
open(name, mode).close()
2561
def create_transform_and_root_trans_id(self):
2562
"""Setup a transform creating a file in limbo"""
2563
tree = self.make_branch_and_tree('.')
2564
tt = TreeTransform(tree)
2565
return tt, tt.create_path("a", tt.root)
2567
def create_transform_and_subdir_trans_id(self):
2568
"""Setup a transform creating a directory containing a file in limbo"""
2569
tree = self.make_branch_and_tree('.')
2570
tt = TreeTransform(tree)
2571
d_trans_id = tt.create_path("d", tt.root)
2572
tt.create_directory(d_trans_id)
2573
f_trans_id = tt.create_path("a", d_trans_id)
2574
tt.adjust_path("a", d_trans_id, f_trans_id)
2575
return tt, f_trans_id
2577
def test_root_create_file_open_raises_before_creation(self):
2578
tt, trans_id = self.create_transform_and_root_trans_id()
2579
self._override_globals_in_method(tt, "create_file",
2580
{"open": self._fake_open_raises_before})
2581
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2582
path = tt._limbo_name(trans_id)
2583
self.assertPathDoesNotExist(path)
2585
self.assertPathDoesNotExist(tt._limbodir)
2587
def test_root_create_file_open_raises_after_creation(self):
2588
tt, trans_id = self.create_transform_and_root_trans_id()
2589
self._override_globals_in_method(tt, "create_file",
2590
{"open": self._fake_open_raises_after})
2591
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2592
path = tt._limbo_name(trans_id)
2593
self.assertPathExists(path)
2595
self.assertPathDoesNotExist(path)
2596
self.assertPathDoesNotExist(tt._limbodir)
2598
def test_subdir_create_file_open_raises_before_creation(self):
2599
tt, trans_id = self.create_transform_and_subdir_trans_id()
2600
self._override_globals_in_method(tt, "create_file",
2601
{"open": self._fake_open_raises_before})
2602
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2603
path = tt._limbo_name(trans_id)
2604
self.assertPathDoesNotExist(path)
2606
self.assertPathDoesNotExist(tt._limbodir)
2608
def test_subdir_create_file_open_raises_after_creation(self):
2609
tt, trans_id = self.create_transform_and_subdir_trans_id()
2610
self._override_globals_in_method(tt, "create_file",
2611
{"open": self._fake_open_raises_after})
2612
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2613
path = tt._limbo_name(trans_id)
2614
self.assertPathExists(path)
2616
self.assertPathDoesNotExist(path)
2617
self.assertPathDoesNotExist(tt._limbodir)
2619
def test_rename_in_limbo_rename_raises_after_rename(self):
2620
tt, trans_id = self.create_transform_and_root_trans_id()
2621
parent1 = tt.new_directory('parent1', tt.root)
2622
child1 = tt.new_file('child1', parent1, 'contents')
2623
parent2 = tt.new_directory('parent2', tt.root)
2625
class FakeOSModule(object):
2626
def rename(self, old, new):
2629
self._override_globals_in_method(tt, "_rename_in_limbo",
2630
{"os": FakeOSModule()})
2632
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2633
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2634
self.assertPathExists(path)
2636
self.assertPathDoesNotExist(path)
2637
self.assertPathDoesNotExist(tt._limbodir)
2639
def test_rename_in_limbo_rename_raises_before_rename(self):
2640
tt, trans_id = self.create_transform_and_root_trans_id()
2641
parent1 = tt.new_directory('parent1', tt.root)
2642
child1 = tt.new_file('child1', parent1, 'contents')
2643
parent2 = tt.new_directory('parent2', tt.root)
2645
class FakeOSModule(object):
2646
def rename(self, old, new):
2648
self._override_globals_in_method(tt, "_rename_in_limbo",
2649
{"os": FakeOSModule()})
2651
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2652
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2653
self.assertPathExists(path)
2655
self.assertPathDoesNotExist(path)
2656
self.assertPathDoesNotExist(tt._limbodir)
2659
class TestTransformMissingParent(tests.TestCaseWithTransport):
2661
def make_tt_with_versioned_dir(self):
2662
wt = self.make_branch_and_tree('.')
2663
self.build_tree(['dir/',])
2664
wt.add(['dir'], ['dir-id'])
2665
wt.commit('Create dir')
2666
tt = TreeTransform(wt)
2667
self.addCleanup(tt.finalize)
2670
def test_resolve_create_parent_for_versioned_file(self):
2671
wt, tt = self.make_tt_with_versioned_dir()
2672
dir_tid = tt.trans_id_tree_file_id('dir-id')
2673
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2674
tt.delete_contents(dir_tid)
2675
tt.unversion_file(dir_tid)
2676
conflicts = resolve_conflicts(tt)
2677
# one conflict for the missing directory, one for the unversioned
2679
self.assertLength(2, conflicts)
2681
def test_non_versioned_file_create_conflict(self):
2682
wt, tt = self.make_tt_with_versioned_dir()
2683
dir_tid = tt.trans_id_tree_file_id('dir-id')
2684
tt.new_file('file', dir_tid, 'Contents')
2685
tt.delete_contents(dir_tid)
2686
tt.unversion_file(dir_tid)
2687
conflicts = resolve_conflicts(tt)
2688
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2689
self.assertLength(1, conflicts)
2690
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2694
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2695
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2697
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2698
('', ''), ('directory', 'directory'), (False, False))
2701
class TestTransformPreview(tests.TestCaseWithTransport):
2703
def create_tree(self):
2704
tree = self.make_branch_and_tree('.')
2705
self.build_tree_contents([('a', 'content 1')])
2706
tree.set_root_id('TREE_ROOT')
2707
tree.add('a', 'a-id')
2708
tree.commit('rev1', rev_id='rev1')
2709
return tree.branch.repository.revision_tree('rev1')
2711
def get_empty_preview(self):
2712
repository = self.make_repository('repo')
2713
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2714
preview = TransformPreview(tree)
2715
self.addCleanup(preview.finalize)
2718
def test_transform_preview(self):
2719
revision_tree = self.create_tree()
2720
preview = TransformPreview(revision_tree)
2721
self.addCleanup(preview.finalize)
2723
def test_transform_preview_tree(self):
2724
revision_tree = self.create_tree()
2725
preview = TransformPreview(revision_tree)
2726
self.addCleanup(preview.finalize)
2727
preview.get_preview_tree()
2729
def test_transform_new_file(self):
2730
revision_tree = self.create_tree()
2731
preview = TransformPreview(revision_tree)
2732
self.addCleanup(preview.finalize)
2733
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2734
preview_tree = preview.get_preview_tree()
2735
self.assertEqual(preview_tree.kind('file2'), 'file')
2737
preview_tree.get_file('file2', 'file2-id').read(), 'content B\n')
2739
def test_diff_preview_tree(self):
2740
revision_tree = self.create_tree()
2741
preview = TransformPreview(revision_tree)
2742
self.addCleanup(preview.finalize)
2743
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2744
preview_tree = preview.get_preview_tree()
2746
show_diff_trees(revision_tree, preview_tree, out)
2747
lines = out.getvalue().splitlines()
2748
self.assertEqual(lines[0], "=== added file 'file2'")
2749
# 3 lines of diff administrivia
2750
self.assertEqual(lines[4], "+content B")
2752
def test_transform_conflicts(self):
2753
revision_tree = self.create_tree()
2754
preview = TransformPreview(revision_tree)
2755
self.addCleanup(preview.finalize)
2756
preview.new_file('a', preview.root, 'content 2')
2757
resolve_conflicts(preview)
2758
trans_id = preview.trans_id_file_id('a-id')
2759
self.assertEqual('a.moved', preview.final_name(trans_id))
2761
def get_tree_and_preview_tree(self):
2762
revision_tree = self.create_tree()
2763
preview = TransformPreview(revision_tree)
2764
self.addCleanup(preview.finalize)
2765
a_trans_id = preview.trans_id_file_id('a-id')
2766
preview.delete_contents(a_trans_id)
2767
preview.create_file('b content', a_trans_id)
2768
preview_tree = preview.get_preview_tree()
2769
return revision_tree, preview_tree
2771
def test_iter_changes(self):
2772
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2773
root = revision_tree.get_root_id()
2774
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2775
(root, root), ('a', 'a'), ('file', 'file'),
2777
list(preview_tree.iter_changes(revision_tree)))
2779
def test_include_unchanged_succeeds(self):
2780
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2781
changes = preview_tree.iter_changes(revision_tree,
2782
include_unchanged=True)
2783
root = revision_tree.get_root_id()
2785
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2787
def test_specific_files(self):
2788
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2789
changes = preview_tree.iter_changes(revision_tree,
2790
specific_files=[''])
2791
self.assertEqual([A_ENTRY], list(changes))
2793
def test_want_unversioned(self):
2794
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2795
changes = preview_tree.iter_changes(revision_tree,
2796
want_unversioned=True)
2797
self.assertEqual([A_ENTRY], list(changes))
2799
def test_ignore_extra_trees_no_specific_files(self):
2800
# extra_trees is harmless without specific_files, so we'll silently
2801
# accept it, even though we won't use it.
2802
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2803
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2805
def test_ignore_require_versioned_no_specific_files(self):
2806
# require_versioned is meaningless without specific_files.
2807
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2808
preview_tree.iter_changes(revision_tree, require_versioned=False)
2810
def test_ignore_pb(self):
2811
# pb could be supported, but TT.iter_changes doesn't support it.
2812
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2813
preview_tree.iter_changes(revision_tree)
2815
def test_kind(self):
2816
revision_tree = self.create_tree()
2817
preview = TransformPreview(revision_tree)
2818
self.addCleanup(preview.finalize)
2819
preview.new_file('file', preview.root, 'contents', 'file-id')
2820
preview.new_directory('directory', preview.root, 'dir-id')
2821
preview_tree = preview.get_preview_tree()
2822
self.assertEqual('file', preview_tree.kind('file'))
2823
self.assertEqual('directory', preview_tree.kind('directory'))
2825
def test_get_file_mtime(self):
2826
preview = self.get_empty_preview()
2827
file_trans_id = preview.new_file('file', preview.root, 'contents',
2829
limbo_path = preview._limbo_name(file_trans_id)
2830
preview_tree = preview.get_preview_tree()
2831
self.assertEqual(os.stat(limbo_path).st_mtime,
2832
preview_tree.get_file_mtime('file', 'file-id'))
2834
def test_get_file_mtime_renamed(self):
2835
work_tree = self.make_branch_and_tree('tree')
2836
self.build_tree(['tree/file'])
2837
work_tree.add('file', 'file-id')
2838
preview = TransformPreview(work_tree)
2839
self.addCleanup(preview.finalize)
2840
file_trans_id = preview.trans_id_tree_file_id('file-id')
2841
preview.adjust_path('renamed', preview.root, file_trans_id)
2842
preview_tree = preview.get_preview_tree()
2843
preview_mtime = preview_tree.get_file_mtime('renamed', 'file-id')
2844
work_mtime = work_tree.get_file_mtime('file', 'file-id')
2846
def test_get_file_size(self):
2847
work_tree = self.make_branch_and_tree('tree')
2848
self.build_tree_contents([('tree/old', 'old')])
2849
work_tree.add('old', 'old-id')
2850
preview = TransformPreview(work_tree)
2851
self.addCleanup(preview.finalize)
2852
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2854
tree = preview.get_preview_tree()
2855
self.assertEqual(len('old'), tree.get_file_size('old'))
2856
self.assertEqual(len('contents'), tree.get_file_size('name'))
2858
def test_get_file(self):
2859
preview = self.get_empty_preview()
2860
preview.new_file('file', preview.root, 'contents', 'file-id')
2861
preview_tree = preview.get_preview_tree()
2862
tree_file = preview_tree.get_file('file')
2864
self.assertEqual('contents', tree_file.read())
2868
def test_get_symlink_target(self):
2869
self.requireFeature(SymlinkFeature)
2870
preview = self.get_empty_preview()
2871
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2872
preview_tree = preview.get_preview_tree()
2873
self.assertEqual('target',
2874
preview_tree.get_symlink_target('symlink'))
2876
def test_all_file_ids(self):
2877
tree = self.make_branch_and_tree('tree')
2878
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2879
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2880
preview = TransformPreview(tree)
2881
self.addCleanup(preview.finalize)
2882
preview.unversion_file(preview.trans_id_file_id('b-id'))
2883
c_trans_id = preview.trans_id_file_id('c-id')
2884
preview.unversion_file(c_trans_id)
2885
preview.version_file('c-id', c_trans_id)
2886
preview_tree = preview.get_preview_tree()
2887
self.assertEqual({'a-id', 'c-id', tree.get_root_id()},
2888
preview_tree.all_file_ids())
2890
def test_path2id_deleted_unchanged(self):
2891
tree = self.make_branch_and_tree('tree')
2892
self.build_tree(['tree/unchanged', 'tree/deleted'])
2893
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2894
preview = TransformPreview(tree)
2895
self.addCleanup(preview.finalize)
2896
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2897
preview_tree = preview.get_preview_tree()
2898
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2899
self.assertIs(None, preview_tree.path2id('deleted'))
2901
def test_path2id_created(self):
2902
tree = self.make_branch_and_tree('tree')
2903
self.build_tree(['tree/unchanged'])
2904
tree.add(['unchanged'], ['unchanged-id'])
2905
preview = TransformPreview(tree)
2906
self.addCleanup(preview.finalize)
2907
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2908
'contents', 'new-id')
2909
preview_tree = preview.get_preview_tree()
2910
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2912
def test_path2id_moved(self):
2913
tree = self.make_branch_and_tree('tree')
2914
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2915
tree.add(['old_parent', 'old_parent/child'],
2916
['old_parent-id', 'child-id'])
2917
preview = TransformPreview(tree)
2918
self.addCleanup(preview.finalize)
2919
new_parent = preview.new_directory('new_parent', preview.root,
2921
preview.adjust_path('child', new_parent,
2922
preview.trans_id_file_id('child-id'))
2923
preview_tree = preview.get_preview_tree()
2924
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2925
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2927
def test_path2id_renamed_parent(self):
2928
tree = self.make_branch_and_tree('tree')
2929
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2930
tree.add(['old_name', 'old_name/child'],
2931
['parent-id', 'child-id'])
2932
preview = TransformPreview(tree)
2933
self.addCleanup(preview.finalize)
2934
preview.adjust_path('new_name', preview.root,
2935
preview.trans_id_file_id('parent-id'))
2936
preview_tree = preview.get_preview_tree()
2937
self.assertIs(None, preview_tree.path2id('old_name/child'))
2938
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2940
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2941
preview_tree = tt.get_preview_tree()
2942
preview_result = list(preview_tree.iter_entries_by_dir(
2946
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2947
self.assertEqual(actual_result, preview_result)
2949
def test_iter_entries_by_dir_new(self):
2950
tree = self.make_branch_and_tree('tree')
2951
tt = TreeTransform(tree)
2952
tt.new_file('new', tt.root, 'contents', 'new-id')
2953
self.assertMatchingIterEntries(tt)
2955
def test_iter_entries_by_dir_deleted(self):
2956
tree = self.make_branch_and_tree('tree')
2957
self.build_tree(['tree/deleted'])
2958
tree.add('deleted', 'deleted-id')
2959
tt = TreeTransform(tree)
2960
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2961
self.assertMatchingIterEntries(tt)
2963
def test_iter_entries_by_dir_unversioned(self):
2964
tree = self.make_branch_and_tree('tree')
2965
self.build_tree(['tree/removed'])
2966
tree.add('removed', 'removed-id')
2967
tt = TreeTransform(tree)
2968
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2969
self.assertMatchingIterEntries(tt)
2971
def test_iter_entries_by_dir_moved(self):
2972
tree = self.make_branch_and_tree('tree')
2973
self.build_tree(['tree/moved', 'tree/new_parent/'])
2974
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2975
tt = TreeTransform(tree)
2976
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2977
tt.trans_id_file_id('moved-id'))
2978
self.assertMatchingIterEntries(tt)
2980
def test_iter_entries_by_dir_specific_file_ids(self):
2981
tree = self.make_branch_and_tree('tree')
2982
tree.set_root_id('tree-root-id')
2983
self.build_tree(['tree/parent/', 'tree/parent/child'])
2984
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2985
tt = TreeTransform(tree)
2986
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2988
def test_symlink_content_summary(self):
2989
self.requireFeature(SymlinkFeature)
2990
preview = self.get_empty_preview()
2991
preview.new_symlink('path', preview.root, 'target', 'path-id')
2992
summary = preview.get_preview_tree().path_content_summary('path')
2993
self.assertEqual(('symlink', None, None, 'target'), summary)
2995
def test_missing_content_summary(self):
2996
preview = self.get_empty_preview()
2997
summary = preview.get_preview_tree().path_content_summary('path')
2998
self.assertEqual(('missing', None, None, None), summary)
3000
def test_deleted_content_summary(self):
3001
tree = self.make_branch_and_tree('tree')
3002
self.build_tree(['tree/path/'])
3004
preview = TransformPreview(tree)
3005
self.addCleanup(preview.finalize)
3006
preview.delete_contents(preview.trans_id_tree_path('path'))
3007
summary = preview.get_preview_tree().path_content_summary('path')
3008
self.assertEqual(('missing', None, None, None), summary)
3010
def test_file_content_summary_executable(self):
3011
preview = self.get_empty_preview()
3012
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
3013
preview.set_executability(True, path_id)
3014
summary = preview.get_preview_tree().path_content_summary('path')
3015
self.assertEqual(4, len(summary))
3016
self.assertEqual('file', summary[0])
3017
# size must be known
3018
self.assertEqual(len('contents'), summary[1])
3020
self.assertEqual(True, summary[2])
3021
# will not have hash (not cheap to determine)
3022
self.assertIs(None, summary[3])
3024
def test_change_executability(self):
3025
tree = self.make_branch_and_tree('tree')
3026
self.build_tree(['tree/path'])
3028
preview = TransformPreview(tree)
3029
self.addCleanup(preview.finalize)
3030
path_id = preview.trans_id_tree_path('path')
3031
preview.set_executability(True, path_id)
3032
summary = preview.get_preview_tree().path_content_summary('path')
3033
self.assertEqual(True, summary[2])
3035
def test_file_content_summary_non_exec(self):
3036
preview = self.get_empty_preview()
3037
preview.new_file('path', preview.root, 'contents', 'path-id')
3038
summary = preview.get_preview_tree().path_content_summary('path')
3039
self.assertEqual(4, len(summary))
3040
self.assertEqual('file', summary[0])
3041
# size must be known
3042
self.assertEqual(len('contents'), summary[1])
3044
self.assertEqual(False, summary[2])
3045
# will not have hash (not cheap to determine)
3046
self.assertIs(None, summary[3])
3048
def test_dir_content_summary(self):
3049
preview = self.get_empty_preview()
3050
preview.new_directory('path', preview.root, 'path-id')
3051
summary = preview.get_preview_tree().path_content_summary('path')
3052
self.assertEqual(('directory', None, None, None), summary)
3054
def test_tree_content_summary(self):
3055
preview = self.get_empty_preview()
3056
path = preview.new_directory('path', preview.root, 'path-id')
3057
preview.set_tree_reference('rev-1', path)
3058
summary = preview.get_preview_tree().path_content_summary('path')
3059
self.assertEqual(4, len(summary))
3060
self.assertEqual('tree-reference', summary[0])
3062
def test_annotate(self):
3063
tree = self.make_branch_and_tree('tree')
3064
self.build_tree_contents([('tree/file', 'a\n')])
3065
tree.add('file', 'file-id')
3066
tree.commit('a', rev_id='one')
3067
self.build_tree_contents([('tree/file', 'a\nb\n')])
3068
preview = TransformPreview(tree)
3069
self.addCleanup(preview.finalize)
3070
file_trans_id = preview.trans_id_file_id('file-id')
3071
preview.delete_contents(file_trans_id)
3072
preview.create_file('a\nb\nc\n', file_trans_id)
3073
preview_tree = preview.get_preview_tree()
3079
annotation = preview_tree.annotate_iter('file', default_revision='me:')
3080
self.assertEqual(expected, annotation)
3082
def test_annotate_missing(self):
3083
preview = self.get_empty_preview()
3084
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3085
preview_tree = preview.get_preview_tree()
3091
annotation = preview_tree.annotate_iter('file', default_revision='me:')
3092
self.assertEqual(expected, annotation)
3094
def test_annotate_rename(self):
3095
tree = self.make_branch_and_tree('tree')
3096
self.build_tree_contents([('tree/file', 'a\n')])
3097
tree.add('file', 'file-id')
3098
tree.commit('a', rev_id='one')
3099
preview = TransformPreview(tree)
3100
self.addCleanup(preview.finalize)
3101
file_trans_id = preview.trans_id_file_id('file-id')
3102
preview.adjust_path('newname', preview.root, file_trans_id)
3103
preview_tree = preview.get_preview_tree()
3107
annotation = preview_tree.annotate_iter('file', default_revision='me:')
3108
self.assertEqual(expected, annotation)
3110
def test_annotate_deleted(self):
3111
tree = self.make_branch_and_tree('tree')
3112
self.build_tree_contents([('tree/file', 'a\n')])
3113
tree.add('file', 'file-id')
3114
tree.commit('a', rev_id='one')
3115
self.build_tree_contents([('tree/file', 'a\nb\n')])
3116
preview = TransformPreview(tree)
3117
self.addCleanup(preview.finalize)
3118
file_trans_id = preview.trans_id_file_id('file-id')
3119
preview.delete_contents(file_trans_id)
3120
preview_tree = preview.get_preview_tree()
3121
annotation = preview_tree.annotate_iter('file', default_revision='me:')
3122
self.assertIs(None, annotation)
3124
def test_stored_kind(self):
3125
preview = self.get_empty_preview()
3126
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3127
preview_tree = preview.get_preview_tree()
3128
self.assertEqual('file', preview_tree.stored_kind('file'))
3130
def test_is_executable(self):
3131
preview = self.get_empty_preview()
3132
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3133
preview.set_executability(True, preview.trans_id_file_id('file-id'))
3134
preview_tree = preview.get_preview_tree()
3135
self.assertEqual(True, preview_tree.is_executable('file'))
3137
def test_get_set_parent_ids(self):
3138
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3139
self.assertEqual([], preview_tree.get_parent_ids())
3140
preview_tree.set_parent_ids(['rev-1'])
3141
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
3143
def test_plan_file_merge(self):
3144
work_a = self.make_branch_and_tree('wta')
3145
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3146
work_a.add('file', 'file-id')
3147
base_id = work_a.commit('base version')
3148
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3149
preview = TransformPreview(work_a)
3150
self.addCleanup(preview.finalize)
3151
trans_id = preview.trans_id_file_id('file-id')
3152
preview.delete_contents(trans_id)
3153
preview.create_file('b\nc\nd\ne\n', trans_id)
3154
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3155
tree_a = preview.get_preview_tree()
3156
tree_a.set_parent_ids([base_id])
3158
('killed-a', 'a\n'),
3159
('killed-b', 'b\n'),
3160
('unchanged', 'c\n'),
3161
('unchanged', 'd\n'),
3164
], list(tree_a.plan_file_merge('file-id', tree_b)))
3166
def test_plan_file_merge_revision_tree(self):
3167
work_a = self.make_branch_and_tree('wta')
3168
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3169
work_a.add('file', 'file-id')
3170
base_id = work_a.commit('base version')
3171
tree_b = work_a.controldir.sprout('wtb').open_workingtree()
3172
preview = TransformPreview(work_a.basis_tree())
3173
self.addCleanup(preview.finalize)
3174
trans_id = preview.trans_id_file_id('file-id')
3175
preview.delete_contents(trans_id)
3176
preview.create_file('b\nc\nd\ne\n', trans_id)
3177
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3178
tree_a = preview.get_preview_tree()
3179
tree_a.set_parent_ids([base_id])
3181
('killed-a', 'a\n'),
3182
('killed-b', 'b\n'),
3183
('unchanged', 'c\n'),
3184
('unchanged', 'd\n'),
3187
], list(tree_a.plan_file_merge('file-id', tree_b)))
3189
def test_walkdirs(self):
3190
preview = self.get_empty_preview()
3191
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3192
# FIXME: new_directory should mark root.
3193
preview.fixup_new_roots()
3194
preview_tree = preview.get_preview_tree()
3195
file_trans_id = preview.new_file('a', preview.root, 'contents',
3197
expected = [(('', 'tree-root'),
3198
[('a', 'a', 'file', None, 'a-id', 'file')])]
3199
self.assertEqual(expected, list(preview_tree.walkdirs()))
3201
def test_extras(self):
3202
work_tree = self.make_branch_and_tree('tree')
3203
self.build_tree(['tree/removed-file', 'tree/existing-file',
3204
'tree/not-removed-file'])
3205
work_tree.add(['removed-file', 'not-removed-file'])
3206
preview = TransformPreview(work_tree)
3207
self.addCleanup(preview.finalize)
3208
preview.new_file('new-file', preview.root, 'contents')
3209
preview.new_file('new-versioned-file', preview.root, 'contents',
3211
tree = preview.get_preview_tree()
3212
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3213
self.assertEqual({'new-file', 'removed-file', 'existing-file'},
3216
def test_merge_into_preview(self):
3217
work_tree = self.make_branch_and_tree('tree')
3218
self.build_tree_contents([('tree/file', 'b\n')])
3219
work_tree.add('file', 'file-id')
3220
work_tree.commit('first commit')
3221
child_tree = work_tree.controldir.sprout('child').open_workingtree()
3222
self.build_tree_contents([('child/file', 'b\nc\n')])
3223
child_tree.commit('child commit')
3224
child_tree.lock_write()
3225
self.addCleanup(child_tree.unlock)
3226
work_tree.lock_write()
3227
self.addCleanup(work_tree.unlock)
3228
preview = TransformPreview(work_tree)
3229
self.addCleanup(preview.finalize)
3230
file_trans_id = preview.trans_id_file_id('file-id')
3231
preview.delete_contents(file_trans_id)
3232
preview.create_file('a\nb\n', file_trans_id)
3233
preview_tree = preview.get_preview_tree()
3234
merger = Merger.from_revision_ids(preview_tree,
3235
child_tree.branch.last_revision(),
3236
other_branch=child_tree.branch,
3237
tree_branch=work_tree.branch)
3238
merger.merge_type = Merge3Merger
3239
tt = merger.make_merger().make_preview_transform()
3240
self.addCleanup(tt.finalize)
3241
final_tree = tt.get_preview_tree()
3244
final_tree.get_file_text(final_tree.id2path('file-id')))
3246
def test_merge_preview_into_workingtree(self):
3247
tree = self.make_branch_and_tree('tree')
3248
tree.set_root_id('TREE_ROOT')
3249
tt = TransformPreview(tree)
3250
self.addCleanup(tt.finalize)
3251
tt.new_file('name', tt.root, 'content', 'file-id')
3252
tree2 = self.make_branch_and_tree('tree2')
3253
tree2.set_root_id('TREE_ROOT')
3254
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3256
merger.merge_type = Merge3Merger
3259
def test_merge_preview_into_workingtree_handles_conflicts(self):
3260
tree = self.make_branch_and_tree('tree')
3261
self.build_tree_contents([('tree/foo', 'bar')])
3262
tree.add('foo', 'foo-id')
3264
tt = TransformPreview(tree)
3265
self.addCleanup(tt.finalize)
3266
trans_id = tt.trans_id_file_id('foo-id')
3267
tt.delete_contents(trans_id)
3268
tt.create_file('baz', trans_id)
3269
tree2 = tree.controldir.sprout('tree2').open_workingtree()
3270
self.build_tree_contents([('tree2/foo', 'qux')])
3271
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3273
merger.merge_type = Merge3Merger
3276
def test_has_filename(self):
3277
wt = self.make_branch_and_tree('tree')
3278
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3279
tt = TransformPreview(wt)
3280
removed_id = tt.trans_id_tree_path('removed')
3281
tt.delete_contents(removed_id)
3282
tt.new_file('new', tt.root, 'contents')
3283
modified_id = tt.trans_id_tree_path('modified')
3284
tt.delete_contents(modified_id)
3285
tt.create_file('modified-contents', modified_id)
3286
self.addCleanup(tt.finalize)
3287
tree = tt.get_preview_tree()
3288
self.assertTrue(tree.has_filename('unmodified'))
3289
self.assertFalse(tree.has_filename('not-present'))
3290
self.assertFalse(tree.has_filename('removed'))
3291
self.assertTrue(tree.has_filename('new'))
3292
self.assertTrue(tree.has_filename('modified'))
3294
def test_is_executable(self):
3295
tree = self.make_branch_and_tree('tree')
3296
preview = TransformPreview(tree)
3297
self.addCleanup(preview.finalize)
3298
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3299
preview_tree = preview.get_preview_tree()
3300
self.assertEqual(False, preview_tree.is_executable('tree/foo', 'baz-id'))
3301
self.assertEqual(False, preview_tree.is_executable('tree/foo'))
3303
def test_commit_preview_tree(self):
3304
tree = self.make_branch_and_tree('tree')
3305
rev_id = tree.commit('rev1')
3306
tree.branch.lock_write()
3307
self.addCleanup(tree.branch.unlock)
3308
tt = TransformPreview(tree)
3309
tt.new_file('file', tt.root, 'contents', 'file_id')
3310
self.addCleanup(tt.finalize)
3311
preview = tt.get_preview_tree()
3312
preview.set_parent_ids([rev_id])
3313
builder = tree.branch.get_commit_builder([rev_id])
3314
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3315
builder.finish_inventory()
3316
rev2_id = builder.commit('rev2')
3317
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3318
self.assertEqual('contents', rev2_tree.get_file_text('file'))
3320
def test_ascii_limbo_paths(self):
3321
self.requireFeature(features.UnicodeFilenameFeature)
3322
branch = self.make_branch('any')
3323
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3324
tt = TransformPreview(tree)
3325
self.addCleanup(tt.finalize)
3326
foo_id = tt.new_directory('', ROOT_PARENT)
3327
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3328
limbo_path = tt._limbo_name(bar_id)
3329
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3332
class FakeSerializer(object):
3333
"""Serializer implementation that simply returns the input.
3335
The input is returned in the order used by pack.ContainerPushParser.
3338
def bytes_record(bytes, names):
3342
class TestSerializeTransform(tests.TestCaseWithTransport):
3344
_test_needs_features = [features.UnicodeFilenameFeature]
3346
def get_preview(self, tree=None):
3348
tree = self.make_branch_and_tree('tree')
3349
tt = TransformPreview(tree)
3350
self.addCleanup(tt.finalize)
3353
def assertSerializesTo(self, expected, tt):
3354
records = list(tt.serialize(FakeSerializer()))
3355
self.assertEqual(expected, records)
3358
def default_attribs():
3363
'_new_executability': {},
3365
'_tree_path_ids': {'': 'new-0'},
3367
'_removed_contents': [],
3368
'_non_present_ids': {},
3371
def make_records(self, attribs, contents):
3373
(((('attribs'),),), bencode.bencode(attribs))]
3374
records.extend([(((n, k),), c) for n, k, c in contents])
3377
def creation_records(self):
3378
attribs = self.default_attribs()
3379
attribs['_id_number'] = 3
3380
attribs['_new_name'] = {
3381
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3382
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3383
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3384
attribs['_new_executability'] = {'new-1': 1}
3386
('new-1', 'file', 'i 1\nbar\n'),
3387
('new-2', 'directory', ''),
3389
return self.make_records(attribs, contents)
3391
def test_serialize_creation(self):
3392
tt = self.get_preview()
3393
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3394
tt.new_directory('qux', tt.root, 'quxx')
3395
self.assertSerializesTo(self.creation_records(), tt)
3397
def test_deserialize_creation(self):
3398
tt = self.get_preview()
3399
tt.deserialize(iter(self.creation_records()))
3400
self.assertEqual(3, tt._id_number)
3401
self.assertEqual({'new-1': u'foo\u1234',
3402
'new-2': 'qux'}, tt._new_name)
3403
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3404
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3405
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3406
self.assertEqual({'new-1': True}, tt._new_executability)
3407
self.assertEqual({'new-1': 'file',
3408
'new-2': 'directory'}, tt._new_contents)
3409
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3411
foo_content = foo_limbo.read()
3414
self.assertEqual('bar', foo_content)
3416
def symlink_creation_records(self):
3417
attribs = self.default_attribs()
3418
attribs['_id_number'] = 2
3419
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3420
attribs['_new_parent'] = {'new-1': 'new-0'}
3421
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3422
return self.make_records(attribs, contents)
3424
def test_serialize_symlink_creation(self):
3425
self.requireFeature(features.SymlinkFeature)
3426
tt = self.get_preview()
3427
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3428
self.assertSerializesTo(self.symlink_creation_records(), tt)
3430
def test_deserialize_symlink_creation(self):
3431
self.requireFeature(features.SymlinkFeature)
3432
tt = self.get_preview()
3433
tt.deserialize(iter(self.symlink_creation_records()))
3434
abspath = tt._limbo_name('new-1')
3435
foo_content = osutils.readlink(abspath)
3436
self.assertEqual(u'bar\u1234', foo_content)
3438
def make_destruction_preview(self):
3439
tree = self.make_branch_and_tree('.')
3440
self.build_tree([u'foo\u1234', 'bar'])
3441
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3442
return self.get_preview(tree)
3444
def destruction_records(self):
3445
attribs = self.default_attribs()
3446
attribs['_id_number'] = 3
3447
attribs['_removed_id'] = ['new-1']
3448
attribs['_removed_contents'] = ['new-2']
3449
attribs['_tree_path_ids'] = {
3451
u'foo\u1234'.encode('utf-8'): 'new-1',
3454
return self.make_records(attribs, [])
3456
def test_serialize_destruction(self):
3457
tt = self.make_destruction_preview()
3458
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3459
tt.unversion_file(foo_trans_id)
3460
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3461
tt.delete_contents(bar_trans_id)
3462
self.assertSerializesTo(self.destruction_records(), tt)
3464
def test_deserialize_destruction(self):
3465
tt = self.make_destruction_preview()
3466
tt.deserialize(iter(self.destruction_records()))
3467
self.assertEqual({u'foo\u1234': 'new-1',
3469
'': tt.root}, tt._tree_path_ids)
3470
self.assertEqual({'new-1': u'foo\u1234',
3472
tt.root: ''}, tt._tree_id_paths)
3473
self.assertEqual({'new-1'}, tt._removed_id)
3474
self.assertEqual({'new-2'}, tt._removed_contents)
3476
def missing_records(self):
3477
attribs = self.default_attribs()
3478
attribs['_id_number'] = 2
3479
attribs['_non_present_ids'] = {
3481
return self.make_records(attribs, [])
3483
def test_serialize_missing(self):
3484
tt = self.get_preview()
3485
boo_trans_id = tt.trans_id_file_id('boo')
3486
self.assertSerializesTo(self.missing_records(), tt)
3488
def test_deserialize_missing(self):
3489
tt = self.get_preview()
3490
tt.deserialize(iter(self.missing_records()))
3491
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3493
def make_modification_preview(self):
3494
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3495
LINES_TWO = 'z\nbb\nx\ndd\n'
3496
tree = self.make_branch_and_tree('tree')
3497
self.build_tree_contents([('tree/file', LINES_ONE)])
3498
tree.add('file', 'file-id')
3499
return self.get_preview(tree), LINES_TWO
3501
def modification_records(self):
3502
attribs = self.default_attribs()
3503
attribs['_id_number'] = 2
3504
attribs['_tree_path_ids'] = {
3507
attribs['_removed_contents'] = ['new-1']
3508
contents = [('new-1', 'file',
3509
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3510
return self.make_records(attribs, contents)
3512
def test_serialize_modification(self):
3513
tt, LINES = self.make_modification_preview()
3514
trans_id = tt.trans_id_file_id('file-id')
3515
tt.delete_contents(trans_id)
3516
tt.create_file(LINES, trans_id)
3517
self.assertSerializesTo(self.modification_records(), tt)
3519
def test_deserialize_modification(self):
3520
tt, LINES = self.make_modification_preview()
3521
tt.deserialize(iter(self.modification_records()))
3522
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3524
def make_kind_change_preview(self):
3525
LINES = 'a\nb\nc\nd\n'
3526
tree = self.make_branch_and_tree('tree')
3527
self.build_tree(['tree/foo/'])
3528
tree.add('foo', 'foo-id')
3529
return self.get_preview(tree), LINES
3531
def kind_change_records(self):
3532
attribs = self.default_attribs()
3533
attribs['_id_number'] = 2
3534
attribs['_tree_path_ids'] = {
3537
attribs['_removed_contents'] = ['new-1']
3538
contents = [('new-1', 'file',
3539
'i 4\na\nb\nc\nd\n\n')]
3540
return self.make_records(attribs, contents)
3542
def test_serialize_kind_change(self):
3543
tt, LINES = self.make_kind_change_preview()
3544
trans_id = tt.trans_id_file_id('foo-id')
3545
tt.delete_contents(trans_id)
3546
tt.create_file(LINES, trans_id)
3547
self.assertSerializesTo(self.kind_change_records(), tt)
3549
def test_deserialize_kind_change(self):
3550
tt, LINES = self.make_kind_change_preview()
3551
tt.deserialize(iter(self.kind_change_records()))
3552
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3554
def make_add_contents_preview(self):
3555
LINES = 'a\nb\nc\nd\n'
3556
tree = self.make_branch_and_tree('tree')
3557
self.build_tree(['tree/foo'])
3559
os.unlink('tree/foo')
3560
return self.get_preview(tree), LINES
3562
def add_contents_records(self):
3563
attribs = self.default_attribs()
3564
attribs['_id_number'] = 2
3565
attribs['_tree_path_ids'] = {
3568
contents = [('new-1', 'file',
3569
'i 4\na\nb\nc\nd\n\n')]
3570
return self.make_records(attribs, contents)
3572
def test_serialize_add_contents(self):
3573
tt, LINES = self.make_add_contents_preview()
3574
trans_id = tt.trans_id_tree_path('foo')
3575
tt.create_file(LINES, trans_id)
3576
self.assertSerializesTo(self.add_contents_records(), tt)
3578
def test_deserialize_add_contents(self):
3579
tt, LINES = self.make_add_contents_preview()
3580
tt.deserialize(iter(self.add_contents_records()))
3581
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3583
def test_get_parents_lines(self):
3584
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3585
LINES_TWO = 'z\nbb\nx\ndd\n'
3586
tree = self.make_branch_and_tree('tree')
3587
self.build_tree_contents([('tree/file', LINES_ONE)])
3588
tree.add('file', 'file-id')
3589
tt = self.get_preview(tree)
3590
trans_id = tt.trans_id_tree_path('file')
3591
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3592
tt._get_parents_lines(trans_id))
3594
def test_get_parents_texts(self):
3595
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3596
LINES_TWO = 'z\nbb\nx\ndd\n'
3597
tree = self.make_branch_and_tree('tree')
3598
self.build_tree_contents([('tree/file', LINES_ONE)])
3599
tree.add('file', 'file-id')
3600
tt = self.get_preview(tree)
3601
trans_id = tt.trans_id_tree_path('file')
3602
self.assertEqual((LINES_ONE,),
3603
tt._get_parents_texts(trans_id))
3606
class TestOrphan(tests.TestCaseWithTransport):
3608
def test_no_orphan_for_transform_preview(self):
3609
tree = self.make_branch_and_tree('tree')
3610
tt = transform.TransformPreview(tree)
3611
self.addCleanup(tt.finalize)
3612
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3614
def _set_orphan_policy(self, wt, policy):
3615
wt.branch.get_config_stack().set('bzr.transform.orphan_policy',
3618
def _prepare_orphan(self, wt):
3619
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3620
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3621
wt.commit('add dir and file ignoring foo')
3622
tt = transform.TreeTransform(wt)
3623
self.addCleanup(tt.finalize)
3624
# dir and bar are deleted
3625
dir_tid = tt.trans_id_tree_path('dir')
3626
file_tid = tt.trans_id_tree_path('dir/file')
3627
orphan_tid = tt.trans_id_tree_path('dir/foo')
3628
tt.delete_contents(file_tid)
3629
tt.unversion_file(file_tid)
3630
tt.delete_contents(dir_tid)
3631
tt.unversion_file(dir_tid)
3632
# There should be a conflict because dir still contain foo
3633
raw_conflicts = tt.find_conflicts()
3634
self.assertLength(1, raw_conflicts)
3635
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3636
return tt, orphan_tid
3638
def test_new_orphan_created(self):
3639
wt = self.make_branch_and_tree('.')
3640
self._set_orphan_policy(wt, 'move')
3641
tt, orphan_tid = self._prepare_orphan(wt)
3644
warnings.append(args[0] % args[1:])
3645
self.overrideAttr(trace, 'warning', warning)
3646
remaining_conflicts = resolve_conflicts(tt)
3647
self.assertEqual(['dir/foo has been orphaned in brz-orphans'],
3649
# Yeah for resolved conflicts !
3650
self.assertLength(0, remaining_conflicts)
3651
# We have a new orphan
3652
self.assertEqual('foo.~1~', tt.final_name(orphan_tid))
3653
self.assertEqual('brz-orphans',
3654
tt.final_name(tt.final_parent(orphan_tid)))
3656
def test_never_orphan(self):
3657
wt = self.make_branch_and_tree('.')
3658
self._set_orphan_policy(wt, 'conflict')
3659
tt, orphan_tid = self._prepare_orphan(wt)
3660
remaining_conflicts = resolve_conflicts(tt)
3661
self.assertLength(1, remaining_conflicts)
3662
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3663
remaining_conflicts.pop())
3665
def test_orphan_error(self):
3666
def bogus_orphan(tt, orphan_id, parent_id):
3667
raise transform.OrphaningError(tt.final_name(orphan_id),
3668
tt.final_name(parent_id))
3669
transform.orphaning_registry.register('bogus', bogus_orphan,
3670
'Raise an error when orphaning')
3671
wt = self.make_branch_and_tree('.')
3672
self._set_orphan_policy(wt, 'bogus')
3673
tt, orphan_tid = self._prepare_orphan(wt)
3674
remaining_conflicts = resolve_conflicts(tt)
3675
self.assertLength(1, remaining_conflicts)
3676
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3677
remaining_conflicts.pop())
3679
def test_unknown_orphan_policy(self):
3680
wt = self.make_branch_and_tree('.')
3681
# Set a fictional policy nobody ever implemented
3682
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3683
tt, orphan_tid = self._prepare_orphan(wt)
3686
warnings.append(args[0] % args[1:])
3687
self.overrideAttr(trace, 'warning', warning)
3688
remaining_conflicts = resolve_conflicts(tt)
3689
# We fallback to the default policy which create a conflict
3690
self.assertLength(1, remaining_conflicts)
3691
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3692
remaining_conflicts.pop())
3693
self.assertLength(1, warnings)
3694
self.assertStartsWith(warnings[0], 'Value "donttouchmypreciouuus" ')
3697
class TestTransformHooks(tests.TestCaseWithTransport):
3700
super(TestTransformHooks, self).setUp()
3701
self.wt = self.make_branch_and_tree('.')
3704
def get_transform(self):
3705
transform = TreeTransform(self.wt)
3706
self.addCleanup(transform.finalize)
3707
return transform, transform.root
3709
def test_pre_commit_hooks(self):
3711
def record_pre_transform(tree, tt):
3712
calls.append((tree, tt))
3713
MutableTree.hooks.install_named_hook('pre_transform',
3714
record_pre_transform, "Pre transform")
3715
transform, root = self.get_transform()
3716
old_root_id = transform.tree_file_id(root)
3718
self.assertEqual(old_root_id, self.wt.get_root_id())
3719
self.assertEqual([(self.wt, transform)], calls)
3721
def test_post_commit_hooks(self):
3723
def record_post_transform(tree, tt):
3724
calls.append((tree, tt))
3725
MutableTree.hooks.install_named_hook('post_transform',
3726
record_post_transform, "Post transform")
3727
transform, root = self.get_transform()
3728
old_root_id = transform.tree_file_id(root)
3730
self.assertEqual(old_root_id, self.wt.get_root_id())
3731
self.assertEqual([(self.wt, transform)], calls)
3734
class TestLinkTree(tests.TestCaseWithTransport):
3736
_test_needs_features = [HardlinkFeature]
3739
tests.TestCaseWithTransport.setUp(self)
3740
self.parent_tree = self.make_branch_and_tree('parent')
3741
self.parent_tree.lock_write()
3742
self.addCleanup(self.parent_tree.unlock)
3743
self.build_tree_contents([('parent/foo', 'bar')])
3744
self.parent_tree.add('foo', 'foo-id')
3745
self.parent_tree.commit('added foo')
3746
child_controldir = self.parent_tree.controldir.sprout('child')
3747
self.child_tree = child_controldir.open_workingtree()
3749
def hardlinked(self):
3750
parent_stat = os.lstat(self.parent_tree.abspath('foo'))
3751
child_stat = os.lstat(self.child_tree.abspath('foo'))
3752
return parent_stat.st_ino == child_stat.st_ino
3754
def test_link_fails_if_modified(self):
3755
"""If the file to be linked has modified text, don't link."""
3756
self.build_tree_contents([('child/foo', 'baz')])
3757
transform.link_tree(self.child_tree, self.parent_tree)
3758
self.assertFalse(self.hardlinked())
3760
def test_link_fails_if_execute_bit_changed(self):
3761
"""If the file to be linked has modified execute bit, don't link."""
3762
tt = TreeTransform(self.child_tree)
3764
trans_id = tt.trans_id_tree_file_id('foo-id')
3765
tt.set_executability(True, trans_id)
3769
transform.link_tree(self.child_tree, self.parent_tree)
3770
self.assertFalse(self.hardlinked())
3772
def test_link_succeeds_if_unmodified(self):
3773
"""If the file to be linked is unmodified, link"""
3774
transform.link_tree(self.child_tree, self.parent_tree)
3775
self.assertTrue(self.hardlinked())