1054
507
transform, root = self.get_transform()
1055
508
wt = transform._tree
1057
self.addCleanup(wt.unlock)
1058
509
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
1060
sac = transform.new_file('set_after_creation', root,
1061
'Set after creation', 'sac')
511
sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
1062
512
transform.set_executability(True, sac)
1063
uws = transform.new_file('unset_without_set', root, 'Unset badly',
513
uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
1065
514
self.assertRaises(KeyError, transform.set_executability, None, uws)
1066
515
transform.apply()
1067
516
self.assertTrue(wt.is_executable('soc'))
1068
517
self.assertTrue(wt.is_executable('sac'))
1070
def test_preserve_mode(self):
1071
"""File mode is preserved when replacing content"""
1072
if sys.platform == 'win32':
1073
raise TestSkipped('chmod has no effect on win32')
1074
transform, root = self.get_transform()
1075
transform.new_file('file1', root, 'contents', 'file1-id', True)
1077
self.wt.lock_write()
1078
self.addCleanup(self.wt.unlock)
1079
self.assertTrue(self.wt.is_executable('file1-id'))
1080
transform, root = self.get_transform()
1081
file1_id = transform.trans_id_tree_file_id('file1-id')
1082
transform.delete_contents(file1_id)
1083
transform.create_file('contents2', file1_id)
1085
self.assertTrue(self.wt.is_executable('file1-id'))
1087
def test__set_mode_stats_correctly(self):
1088
"""_set_mode stats to determine file mode."""
1089
if sys.platform == 'win32':
1090
raise TestSkipped('chmod has no effect on win32')
1094
def instrumented_stat(path):
1095
stat_paths.append(path)
1096
return real_stat(path)
1098
transform, root = self.get_transform()
1100
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
1101
file_id='bar-id-1', executable=False)
1104
transform, root = self.get_transform()
1105
bar1_id = transform.trans_id_tree_path('bar')
1106
bar2_id = transform.trans_id_tree_path('bar2')
1108
os.stat = instrumented_stat
1109
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
1112
transform.finalize()
1114
bar1_abspath = self.wt.abspath('bar')
1115
self.assertEqual([bar1_abspath], stat_paths)
1117
def test_iter_changes(self):
1118
self.wt.set_root_id('eert_toor')
1119
transform, root = self.get_transform()
1120
transform.new_file('old', root, 'blah', 'id-1', True)
1122
transform, root = self.get_transform()
1124
self.assertEqual([], list(transform.iter_changes()))
1125
old = transform.trans_id_tree_file_id('id-1')
1126
transform.unversion_file(old)
1127
self.assertEqual([('id-1', ('old', None), False, (True, False),
1128
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1129
(True, True))], list(transform.iter_changes()))
1130
transform.new_directory('new', root, 'id-1')
1131
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
1132
('eert_toor', 'eert_toor'), ('old', 'new'),
1133
('file', 'directory'),
1134
(True, False))], list(transform.iter_changes()))
1136
transform.finalize()
1138
def test_iter_changes_new(self):
1139
self.wt.set_root_id('eert_toor')
1140
transform, root = self.get_transform()
1141
transform.new_file('old', root, 'blah')
1143
transform, root = self.get_transform()
1145
old = transform.trans_id_tree_path('old')
1146
transform.version_file('id-1', old)
1147
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
1148
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1149
(False, False))], list(transform.iter_changes()))
1151
transform.finalize()
1153
def test_iter_changes_modifications(self):
1154
self.wt.set_root_id('eert_toor')
1155
transform, root = self.get_transform()
1156
transform.new_file('old', root, 'blah', 'id-1')
1157
transform.new_file('new', root, 'blah')
1158
transform.new_directory('subdir', root, 'subdir-id')
1160
transform, root = self.get_transform()
1162
old = transform.trans_id_tree_path('old')
1163
subdir = transform.trans_id_tree_file_id('subdir-id')
1164
new = transform.trans_id_tree_path('new')
1165
self.assertEqual([], list(transform.iter_changes()))
1168
transform.delete_contents(old)
1169
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1170
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1171
(False, False))], list(transform.iter_changes()))
1174
transform.create_file('blah', old)
1175
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1176
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1177
(False, False))], list(transform.iter_changes()))
1178
transform.cancel_deletion(old)
1179
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1180
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1181
(False, False))], list(transform.iter_changes()))
1182
transform.cancel_creation(old)
1184
# move file_id to a different file
1185
self.assertEqual([], list(transform.iter_changes()))
1186
transform.unversion_file(old)
1187
transform.version_file('id-1', new)
1188
transform.adjust_path('old', root, new)
1189
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1190
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1191
(False, False))], list(transform.iter_changes()))
1192
transform.cancel_versioning(new)
1193
transform._removed_id = set()
1196
self.assertEqual([], list(transform.iter_changes()))
1197
transform.set_executability(True, old)
1198
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1199
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1200
(False, True))], list(transform.iter_changes()))
1201
transform.set_executability(None, old)
1204
self.assertEqual([], list(transform.iter_changes()))
1205
transform.adjust_path('new', root, old)
1206
transform._new_parent = {}
1207
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1208
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1209
(False, False))], list(transform.iter_changes()))
1210
transform._new_name = {}
1213
self.assertEqual([], list(transform.iter_changes()))
1214
transform.adjust_path('new', subdir, old)
1215
transform._new_name = {}
1216
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1217
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1218
('file', 'file'), (False, False))],
1219
list(transform.iter_changes()))
1220
transform._new_path = {}
1223
transform.finalize()
1225
def test_iter_changes_modified_bleed(self):
1226
self.wt.set_root_id('eert_toor')
1227
"""Modified flag should not bleed from one change to another"""
1228
# unfortunately, we have no guarantee that file1 (which is modified)
1229
# will be applied before file2. And if it's applied after file2, it
1230
# obviously can't bleed into file2's change output. But for now, it
1232
transform, root = self.get_transform()
1233
transform.new_file('file1', root, 'blah', 'id-1')
1234
transform.new_file('file2', root, 'blah', 'id-2')
1236
transform, root = self.get_transform()
1238
transform.delete_contents(transform.trans_id_file_id('id-1'))
1239
transform.set_executability(True,
1240
transform.trans_id_file_id('id-2'))
1241
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1242
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1243
('file', None), (False, False)),
1244
('id-2', (u'file2', u'file2'), False, (True, True),
1245
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1246
('file', 'file'), (False, True))],
1247
list(transform.iter_changes()))
1249
transform.finalize()
1251
def test_iter_changes_move_missing(self):
1252
"""Test moving ids with no files around"""
1253
self.wt.set_root_id('toor_eert')
1254
# Need two steps because versioning a non-existant file is a conflict.
1255
transform, root = self.get_transform()
1256
transform.new_directory('floater', root, 'floater-id')
1258
transform, root = self.get_transform()
1259
transform.delete_contents(transform.trans_id_tree_path('floater'))
1261
transform, root = self.get_transform()
1262
floater = transform.trans_id_tree_path('floater')
1264
transform.adjust_path('flitter', root, floater)
1265
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1266
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1267
(None, None), (False, False))], list(transform.iter_changes()))
1269
transform.finalize()
1271
def test_iter_changes_pointless(self):
1272
"""Ensure that no-ops are not treated as modifications"""
1273
self.wt.set_root_id('eert_toor')
1274
transform, root = self.get_transform()
1275
transform.new_file('old', root, 'blah', 'id-1')
1276
transform.new_directory('subdir', root, 'subdir-id')
1278
transform, root = self.get_transform()
1280
old = transform.trans_id_tree_path('old')
1281
subdir = transform.trans_id_tree_file_id('subdir-id')
1282
self.assertEqual([], list(transform.iter_changes()))
1283
transform.delete_contents(subdir)
1284
transform.create_directory(subdir)
1285
transform.set_executability(False, old)
1286
transform.unversion_file(old)
1287
transform.version_file('id-1', old)
1288
transform.adjust_path('old', root, old)
1289
self.assertEqual([], list(transform.iter_changes()))
1291
transform.finalize()
1293
def test_rename_count(self):
1294
transform, root = self.get_transform()
1295
transform.new_file('name1', root, 'contents')
1296
self.assertEqual(transform.rename_count, 0)
1298
self.assertEqual(transform.rename_count, 1)
1299
transform2, root = self.get_transform()
1300
transform2.adjust_path('name2', root,
1301
transform2.trans_id_tree_path('name1'))
1302
self.assertEqual(transform2.rename_count, 0)
1304
self.assertEqual(transform2.rename_count, 2)
1306
def test_change_parent(self):
1307
"""Ensure that after we change a parent, the results are still right.
1309
Renames and parent changes on pending transforms can happen as part
1310
of conflict resolution, and are explicitly permitted by the
1313
This test ensures they work correctly with the rename-avoidance
1316
transform, root = self.get_transform()
1317
parent1 = transform.new_directory('parent1', root)
1318
child1 = transform.new_file('child1', parent1, 'contents')
1319
parent2 = transform.new_directory('parent2', root)
1320
transform.adjust_path('child1', parent2, child1)
1322
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1323
self.assertPathExists(self.wt.abspath('parent2/child1'))
1324
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1325
# no rename for child1 (counting only renames during apply)
1326
self.assertEqual(2, transform.rename_count)
1328
def test_cancel_parent(self):
1329
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1331
This is like the test_change_parent, except that we cancel the parent
1332
before adjusting the path. The transform must detect that the
1333
directory is non-empty, and move children to safe locations.
1335
transform, root = self.get_transform()
1336
parent1 = transform.new_directory('parent1', root)
1337
child1 = transform.new_file('child1', parent1, 'contents')
1338
child2 = transform.new_file('child2', parent1, 'contents')
1340
transform.cancel_creation(parent1)
1342
self.fail('Failed to move child1 before deleting parent1')
1343
transform.cancel_creation(child2)
1344
transform.create_directory(parent1)
1346
transform.cancel_creation(parent1)
1347
# If the transform incorrectly believes that child2 is still in
1348
# parent1's limbo directory, it will try to rename it and fail
1349
# because was already moved by the first cancel_creation.
1351
self.fail('Transform still thinks child2 is a child of parent1')
1352
parent2 = transform.new_directory('parent2', root)
1353
transform.adjust_path('child1', parent2, child1)
1355
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1356
self.assertPathExists(self.wt.abspath('parent2/child1'))
1357
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1358
self.assertEqual(2, transform.rename_count)
1360
def test_adjust_and_cancel(self):
1361
"""Make sure adjust_path keeps track of limbo children properly"""
1362
transform, root = self.get_transform()
1363
parent1 = transform.new_directory('parent1', root)
1364
child1 = transform.new_file('child1', parent1, 'contents')
1365
parent2 = transform.new_directory('parent2', root)
1366
transform.adjust_path('child1', parent2, child1)
1367
transform.cancel_creation(child1)
1369
transform.cancel_creation(parent1)
1370
# if the transform thinks child1 is still in parent1's limbo
1371
# directory, it will attempt to move it and fail.
1373
self.fail('Transform still thinks child1 is a child of parent1')
1374
transform.finalize()
1376
def test_noname_contents(self):
1377
"""TreeTransform should permit deferring naming files."""
1378
transform, root = self.get_transform()
1379
parent = transform.trans_id_file_id('parent-id')
1381
transform.create_directory(parent)
1383
self.fail("Can't handle contents with no name")
1384
transform.finalize()
1386
def test_noname_contents_nested(self):
1387
"""TreeTransform should permit deferring naming files."""
1388
transform, root = self.get_transform()
1389
parent = transform.trans_id_file_id('parent-id')
1391
transform.create_directory(parent)
1393
self.fail("Can't handle contents with no name")
1394
child = transform.new_directory('child', parent)
1395
transform.adjust_path('parent', root, parent)
1397
self.assertPathExists(self.wt.abspath('parent/child'))
1398
self.assertEqual(1, transform.rename_count)
1400
def test_reuse_name(self):
1401
"""Avoid reusing the same limbo name for different files"""
1402
transform, root = self.get_transform()
1403
parent = transform.new_directory('parent', root)
1404
child1 = transform.new_directory('child', parent)
1406
child2 = transform.new_directory('child', parent)
1408
self.fail('Tranform tried to use the same limbo name twice')
1409
transform.adjust_path('child2', parent, child2)
1411
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1412
# child2 is put into top-level limbo because child1 has already
1413
# claimed the direct limbo path when child2 is created. There is no
1414
# advantage in renaming files once they're in top-level limbo, except
1416
self.assertEqual(2, transform.rename_count)
1418
def test_reuse_when_first_moved(self):
1419
"""Don't avoid direct paths when it is safe to use them"""
1420
transform, root = self.get_transform()
1421
parent = transform.new_directory('parent', root)
1422
child1 = transform.new_directory('child', parent)
1423
transform.adjust_path('child1', parent, child1)
1424
child2 = transform.new_directory('child', parent)
1426
# limbo/new-1 => parent
1427
self.assertEqual(1, transform.rename_count)
1429
def test_reuse_after_cancel(self):
1430
"""Don't avoid direct paths when it is safe to use them"""
1431
transform, root = self.get_transform()
1432
parent2 = transform.new_directory('parent2', root)
1433
child1 = transform.new_directory('child1', parent2)
1434
transform.cancel_creation(parent2)
1435
transform.create_directory(parent2)
1436
child2 = transform.new_directory('child1', parent2)
1437
transform.adjust_path('child2', parent2, child1)
1439
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1440
self.assertEqual(2, transform.rename_count)
1442
def test_finalize_order(self):
1443
"""Finalize must be done in child-to-parent order"""
1444
transform, root = self.get_transform()
1445
parent = transform.new_directory('parent', root)
1446
child = transform.new_directory('child', parent)
1448
transform.finalize()
1450
self.fail('Tried to remove parent before child1')
1452
def test_cancel_with_cancelled_child_should_succeed(self):
1453
transform, root = self.get_transform()
1454
parent = transform.new_directory('parent', root)
1455
child = transform.new_directory('child', parent)
1456
transform.cancel_creation(child)
1457
transform.cancel_creation(parent)
1458
transform.finalize()
1460
def test_rollback_on_directory_clash(self):
1462
wt = self.make_branch_and_tree('.')
1463
tt = TreeTransform(wt) # TreeTransform obtains write lock
1465
foo = tt.new_directory('foo', tt.root)
1466
tt.new_file('bar', foo, 'foobar')
1467
baz = tt.new_directory('baz', tt.root)
1468
tt.new_file('qux', baz, 'quux')
1469
# Ask for a rename 'foo' -> 'baz'
1470
tt.adjust_path('baz', tt.root, foo)
1471
# Lie to tt that we've already resolved all conflicts.
1472
tt.apply(no_conflicts=True)
1476
# The rename will fail because the target directory is not empty (but
1477
# raises FileExists anyway).
1478
err = self.assertRaises(errors.FileExists, tt_helper)
1479
self.assertContainsRe(str(err),
1480
"^File exists: .+/baz")
1482
def test_two_directories_clash(self):
1484
wt = self.make_branch_and_tree('.')
1485
tt = TreeTransform(wt) # TreeTransform obtains write lock
1487
foo_1 = tt.new_directory('foo', tt.root)
1488
tt.new_directory('bar', foo_1)
1489
# Adding the same directory with a different content
1490
foo_2 = tt.new_directory('foo', tt.root)
1491
tt.new_directory('baz', foo_2)
1492
# Lie to tt that we've already resolved all conflicts.
1493
tt.apply(no_conflicts=True)
1497
err = self.assertRaises(errors.FileExists, tt_helper)
1498
self.assertContainsRe(str(err),
1499
"^File exists: .+/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.assertContainsRe(str(err),
1518
"^File exists: .+/foo")
1520
def test_file_to_directory(self):
1521
wt = self.make_branch_and_tree('.')
1522
self.build_tree(['foo'])
1525
tt = TreeTransform(wt)
1526
self.addCleanup(tt.finalize)
1527
foo_trans_id = tt.trans_id_tree_path("foo")
1528
tt.delete_contents(foo_trans_id)
1529
tt.create_directory(foo_trans_id)
1530
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1531
tt.create_file(["aa\n"], bar_trans_id)
1532
tt.version_file("bar-1", bar_trans_id)
1534
self.assertPathExists("foo/bar")
1537
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1542
changes = wt.changes_from(wt.basis_tree())
1543
self.assertFalse(changes.has_changed(), changes)
1545
def test_file_to_symlink(self):
1546
self.requireFeature(SymlinkFeature)
1547
wt = self.make_branch_and_tree('.')
1548
self.build_tree(['foo'])
1551
tt = TreeTransform(wt)
1552
self.addCleanup(tt.finalize)
1553
foo_trans_id = tt.trans_id_tree_path("foo")
1554
tt.delete_contents(foo_trans_id)
1555
tt.create_symlink("bar", foo_trans_id)
1557
self.assertPathExists("foo")
1559
self.addCleanup(wt.unlock)
1560
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1563
def test_dir_to_file(self):
1564
wt = self.make_branch_and_tree('.')
1565
self.build_tree(['foo/', 'foo/bar'])
1566
wt.add(['foo', 'foo/bar'])
1568
tt = TreeTransform(wt)
1569
self.addCleanup(tt.finalize)
1570
foo_trans_id = tt.trans_id_tree_path("foo")
1571
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1572
tt.delete_contents(foo_trans_id)
1573
tt.delete_versioned(bar_trans_id)
1574
tt.create_file(["aa\n"], foo_trans_id)
1576
self.assertPathExists("foo")
1578
self.addCleanup(wt.unlock)
1579
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1582
def test_dir_to_hardlink(self):
1583
self.requireFeature(HardlinkFeature)
1584
wt = self.make_branch_and_tree('.')
1585
self.build_tree(['foo/', 'foo/bar'])
1586
wt.add(['foo', 'foo/bar'])
1588
tt = TreeTransform(wt)
1589
self.addCleanup(tt.finalize)
1590
foo_trans_id = tt.trans_id_tree_path("foo")
1591
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1592
tt.delete_contents(foo_trans_id)
1593
tt.delete_versioned(bar_trans_id)
1594
self.build_tree(['baz'])
1595
tt.create_hardlink("baz", foo_trans_id)
1597
self.assertPathExists("foo")
1598
self.assertPathExists("baz")
1600
self.addCleanup(wt.unlock)
1601
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1604
def test_no_final_path(self):
1605
transform, root = self.get_transform()
1606
trans_id = transform.trans_id_file_id('foo')
1607
transform.create_file('bar', trans_id)
1608
transform.cancel_creation(trans_id)
1611
def test_create_from_tree(self):
1612
tree1 = self.make_branch_and_tree('tree1')
1613
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1614
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1615
tree2 = self.make_branch_and_tree('tree2')
1616
tt = TreeTransform(tree2)
1617
foo_trans_id = tt.create_path('foo', tt.root)
1618
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1619
bar_trans_id = tt.create_path('bar', tt.root)
1620
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1622
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1623
self.assertFileEqual('baz', 'tree2/bar')
1625
def test_create_from_tree_bytes(self):
1626
"""Provided lines are used instead of tree content."""
1627
tree1 = self.make_branch_and_tree('tree1')
1628
self.build_tree_contents([('tree1/foo', 'bar'),])
1629
tree1.add('foo', 'foo-id')
1630
tree2 = self.make_branch_and_tree('tree2')
1631
tt = TreeTransform(tree2)
1632
foo_trans_id = tt.create_path('foo', tt.root)
1633
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1635
self.assertFileEqual('qux', 'tree2/foo')
1637
def test_create_from_tree_symlink(self):
1638
self.requireFeature(SymlinkFeature)
1639
tree1 = self.make_branch_and_tree('tree1')
1640
os.symlink('bar', 'tree1/foo')
1641
tree1.add('foo', 'foo-id')
1642
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1643
foo_trans_id = tt.create_path('foo', tt.root)
1644
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1646
self.assertEqual('bar', os.readlink('tree2/foo'))
1649
520
class TransformGroup(object):
1651
def __init__(self, dirname, root_id):
521
def __init__(self, dirname):
1652
522
self.name = dirname
1653
523
os.mkdir(dirname)
1654
524
self.wt = BzrDir.create_standalone_workingtree(dirname)
1655
self.wt.set_root_id(root_id)
1656
525
self.b = self.wt.branch
1657
526
self.tt = TreeTransform(self.wt)
1658
527
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1661
529
def conflict_text(tree, merge):
1662
530
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1663
531
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1666
class TestInventoryAltered(tests.TestCaseWithTransport):
1668
def test_inventory_altered_unchanged(self):
1669
tree = self.make_branch_and_tree('tree')
1670
self.build_tree(['tree/foo'])
1671
tree.add('foo', 'foo-id')
1672
with TransformPreview(tree) as tt:
1673
self.assertEqual([], tt._inventory_altered())
1675
def test_inventory_altered_changed_parent_id(self):
1676
tree = self.make_branch_and_tree('tree')
1677
self.build_tree(['tree/foo'])
1678
tree.add('foo', 'foo-id')
1679
with TransformPreview(tree) as tt:
1680
tt.unversion_file(tt.root)
1681
tt.version_file('new-id', tt.root)
1682
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1683
foo_tuple = ('foo', foo_trans_id)
1684
root_tuple = ('', tt.root)
1685
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1687
def test_inventory_altered_noop_changed_parent_id(self):
1688
tree = self.make_branch_and_tree('tree')
1689
self.build_tree(['tree/foo'])
1690
tree.add('foo', 'foo-id')
1691
with TransformPreview(tree) as tt:
1692
tt.unversion_file(tt.root)
1693
tt.version_file(tree.get_root_id(), tt.root)
1694
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1695
self.assertEqual([], tt._inventory_altered())
1698
534
class TestTransformMerge(TestCaseInTempDir):
1700
535
def test_text_merge(self):
1701
root_id = generate_ids.gen_root_id()
1702
base = TransformGroup("base", root_id)
536
base = TransformGroup("base")
1703
537
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1704
538
base.tt.new_file('b', base.root, 'b1', 'b')
1705
539
base.tt.new_file('c', base.root, 'c', 'c')
1894
724
a.add(['foo', 'foo/bar', 'foo/baz'])
1895
725
a.commit('initial commit')
1896
726
b = BzrDir.create_standalone_workingtree('b')
1897
basis = a.basis_tree()
1899
self.addCleanup(basis.unlock)
1900
build_tree(basis, b)
727
build_tree(a.basis_tree(), b)
1901
728
self.assertIs(os.path.isdir('b/foo'), True)
1902
729
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1903
730
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1905
def test_build_with_references(self):
1906
tree = self.make_branch_and_tree('source',
1907
format='dirstate-with-subtree')
1908
subtree = self.make_branch_and_tree('source/subtree',
1909
format='dirstate-with-subtree')
1910
tree.add_reference(subtree)
1911
tree.commit('a revision')
1912
tree.branch.create_checkout('target')
1913
self.assertPathExists('target')
1914
self.assertPathExists('target/subtree')
1916
def test_file_conflict_handling(self):
1917
"""Ensure that when building trees, conflict handling is done"""
1918
source = self.make_branch_and_tree('source')
1919
target = self.make_branch_and_tree('target')
1920
self.build_tree(['source/file', 'target/file'])
1921
source.add('file', 'new-file')
1922
source.commit('added file')
1923
build_tree(source.basis_tree(), target)
1924
self.assertEqual([DuplicateEntry('Moved existing file to',
1925
'file.moved', 'file', None, 'new-file')],
1927
target2 = self.make_branch_and_tree('target2')
1928
target_file = file('target2/file', 'wb')
1930
source_file = file('source/file', 'rb')
1932
target_file.write(source_file.read())
1937
build_tree(source.basis_tree(), target2)
1938
self.assertEqual([], target2.conflicts())
1940
def test_symlink_conflict_handling(self):
1941
"""Ensure that when building trees, conflict handling is done"""
1942
self.requireFeature(SymlinkFeature)
1943
source = self.make_branch_and_tree('source')
1944
os.symlink('foo', 'source/symlink')
1945
source.add('symlink', 'new-symlink')
1946
source.commit('added file')
1947
target = self.make_branch_and_tree('target')
1948
os.symlink('bar', 'target/symlink')
1949
build_tree(source.basis_tree(), target)
1950
self.assertEqual([DuplicateEntry('Moved existing file to',
1951
'symlink.moved', 'symlink', None, 'new-symlink')],
1953
target = self.make_branch_and_tree('target2')
1954
os.symlink('foo', 'target2/symlink')
1955
build_tree(source.basis_tree(), target)
1956
self.assertEqual([], target.conflicts())
1958
def test_directory_conflict_handling(self):
1959
"""Ensure that when building trees, conflict handling is done"""
1960
source = self.make_branch_and_tree('source')
1961
target = self.make_branch_and_tree('target')
1962
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1963
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1964
source.commit('added file')
1965
build_tree(source.basis_tree(), target)
1966
self.assertEqual([], target.conflicts())
1967
self.assertPathExists('target/dir1/file')
1969
# Ensure contents are merged
1970
target = self.make_branch_and_tree('target2')
1971
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1972
build_tree(source.basis_tree(), target)
1973
self.assertEqual([], target.conflicts())
1974
self.assertPathExists('target2/dir1/file2')
1975
self.assertPathExists('target2/dir1/file')
1977
# Ensure new contents are suppressed for existing branches
1978
target = self.make_branch_and_tree('target3')
1979
self.make_branch('target3/dir1')
1980
self.build_tree(['target3/dir1/file2'])
1981
build_tree(source.basis_tree(), target)
1982
self.assertPathDoesNotExist('target3/dir1/file')
1983
self.assertPathExists('target3/dir1/file2')
1984
self.assertPathExists('target3/dir1.diverted/file')
1985
self.assertEqual([DuplicateEntry('Diverted to',
1986
'dir1.diverted', 'dir1', 'new-dir1', None)],
1989
target = self.make_branch_and_tree('target4')
1990
self.build_tree(['target4/dir1/'])
1991
self.make_branch('target4/dir1/file')
1992
build_tree(source.basis_tree(), target)
1993
self.assertPathExists('target4/dir1/file')
1994
self.assertEqual('directory', file_kind('target4/dir1/file'))
1995
self.assertPathExists('target4/dir1/file.diverted')
1996
self.assertEqual([DuplicateEntry('Diverted to',
1997
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
2000
def test_mixed_conflict_handling(self):
2001
"""Ensure that when building trees, conflict handling is done"""
2002
source = self.make_branch_and_tree('source')
2003
target = self.make_branch_and_tree('target')
2004
self.build_tree(['source/name', 'target/name/'])
2005
source.add('name', 'new-name')
2006
source.commit('added file')
2007
build_tree(source.basis_tree(), target)
2008
self.assertEqual([DuplicateEntry('Moved existing file to',
2009
'name.moved', 'name', None, 'new-name')], target.conflicts())
2011
def test_raises_in_populated(self):
2012
source = self.make_branch_and_tree('source')
2013
self.build_tree(['source/name'])
2015
source.commit('added name')
2016
target = self.make_branch_and_tree('target')
2017
self.build_tree(['target/name'])
2019
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2020
build_tree, source.basis_tree(), target)
2022
def test_build_tree_rename_count(self):
2023
source = self.make_branch_and_tree('source')
2024
self.build_tree(['source/file1', 'source/dir1/'])
2025
source.add(['file1', 'dir1'])
2026
source.commit('add1')
2027
target1 = self.make_branch_and_tree('target1')
2028
transform_result = build_tree(source.basis_tree(), target1)
2029
self.assertEqual(2, transform_result.rename_count)
2031
self.build_tree(['source/dir1/file2'])
2032
source.add(['dir1/file2'])
2033
source.commit('add3')
2034
target2 = self.make_branch_and_tree('target2')
2035
transform_result = build_tree(source.basis_tree(), target2)
2036
# children of non-root directories should not be renamed
2037
self.assertEqual(2, transform_result.rename_count)
2039
def create_ab_tree(self):
2040
"""Create a committed test tree with two files"""
2041
source = self.make_branch_and_tree('source')
2042
self.build_tree_contents([('source/file1', 'A')])
2043
self.build_tree_contents([('source/file2', 'B')])
2044
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2045
source.commit('commit files')
2047
self.addCleanup(source.unlock)
2050
def test_build_tree_accelerator_tree(self):
2051
source = self.create_ab_tree()
2052
self.build_tree_contents([('source/file2', 'C')])
2054
real_source_get_file = source.get_file
2055
def get_file(file_id, path=None):
2056
calls.append(file_id)
2057
return real_source_get_file(file_id, path)
2058
source.get_file = get_file
2059
target = self.make_branch_and_tree('target')
2060
revision_tree = source.basis_tree()
2061
revision_tree.lock_read()
2062
self.addCleanup(revision_tree.unlock)
2063
build_tree(revision_tree, target, source)
2064
self.assertEqual(['file1-id'], calls)
2066
self.addCleanup(target.unlock)
2067
self.assertEqual([], list(target.iter_changes(revision_tree)))
2069
def test_build_tree_accelerator_tree_observes_sha1(self):
2070
source = self.create_ab_tree()
2071
sha1 = osutils.sha_string('A')
2072
target = self.make_branch_and_tree('target')
2074
self.addCleanup(target.unlock)
2075
state = target.current_dirstate()
2076
state._cutoff_time = time.time() + 60
2077
build_tree(source.basis_tree(), target, source)
2078
entry = state._get_entry(0, path_utf8='file1')
2079
self.assertEqual(sha1, entry[1][0][1])
2081
def test_build_tree_accelerator_tree_missing_file(self):
2082
source = self.create_ab_tree()
2083
os.unlink('source/file1')
2084
source.remove(['file2'])
2085
target = self.make_branch_and_tree('target')
2086
revision_tree = source.basis_tree()
2087
revision_tree.lock_read()
2088
self.addCleanup(revision_tree.unlock)
2089
build_tree(revision_tree, target, source)
2091
self.addCleanup(target.unlock)
2092
self.assertEqual([], list(target.iter_changes(revision_tree)))
2094
def test_build_tree_accelerator_wrong_kind(self):
2095
self.requireFeature(SymlinkFeature)
2096
source = self.make_branch_and_tree('source')
2097
self.build_tree_contents([('source/file1', '')])
2098
self.build_tree_contents([('source/file2', '')])
2099
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2100
source.commit('commit files')
2101
os.unlink('source/file2')
2102
self.build_tree_contents([('source/file2/', 'C')])
2103
os.unlink('source/file1')
2104
os.symlink('file2', 'source/file1')
2106
real_source_get_file = source.get_file
2107
def get_file(file_id, path=None):
2108
calls.append(file_id)
2109
return real_source_get_file(file_id, path)
2110
source.get_file = get_file
2111
target = self.make_branch_and_tree('target')
2112
revision_tree = source.basis_tree()
2113
revision_tree.lock_read()
2114
self.addCleanup(revision_tree.unlock)
2115
build_tree(revision_tree, target, source)
2116
self.assertEqual([], calls)
2118
self.addCleanup(target.unlock)
2119
self.assertEqual([], list(target.iter_changes(revision_tree)))
2121
def test_build_tree_hardlink(self):
2122
self.requireFeature(HardlinkFeature)
2123
source = self.create_ab_tree()
2124
target = self.make_branch_and_tree('target')
2125
revision_tree = source.basis_tree()
2126
revision_tree.lock_read()
2127
self.addCleanup(revision_tree.unlock)
2128
build_tree(revision_tree, target, source, hardlink=True)
2130
self.addCleanup(target.unlock)
2131
self.assertEqual([], list(target.iter_changes(revision_tree)))
2132
source_stat = os.stat('source/file1')
2133
target_stat = os.stat('target/file1')
2134
self.assertEqual(source_stat, target_stat)
2136
# Explicitly disallowing hardlinks should prevent them.
2137
target2 = self.make_branch_and_tree('target2')
2138
build_tree(revision_tree, target2, source, hardlink=False)
2140
self.addCleanup(target2.unlock)
2141
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2142
source_stat = os.stat('source/file1')
2143
target2_stat = os.stat('target2/file1')
2144
self.assertNotEqual(source_stat, target2_stat)
2146
def test_build_tree_accelerator_tree_moved(self):
2147
source = self.make_branch_and_tree('source')
2148
self.build_tree_contents([('source/file1', 'A')])
2149
source.add(['file1'], ['file1-id'])
2150
source.commit('commit files')
2151
source.rename_one('file1', 'file2')
2153
self.addCleanup(source.unlock)
2154
target = self.make_branch_and_tree('target')
2155
revision_tree = source.basis_tree()
2156
revision_tree.lock_read()
2157
self.addCleanup(revision_tree.unlock)
2158
build_tree(revision_tree, target, source)
2160
self.addCleanup(target.unlock)
2161
self.assertEqual([], list(target.iter_changes(revision_tree)))
2163
def test_build_tree_hardlinks_preserve_execute(self):
2164
self.requireFeature(HardlinkFeature)
2165
source = self.create_ab_tree()
2166
tt = TreeTransform(source)
2167
trans_id = tt.trans_id_tree_file_id('file1-id')
2168
tt.set_executability(True, trans_id)
2170
self.assertTrue(source.is_executable('file1-id'))
2171
target = self.make_branch_and_tree('target')
2172
revision_tree = source.basis_tree()
2173
revision_tree.lock_read()
2174
self.addCleanup(revision_tree.unlock)
2175
build_tree(revision_tree, target, source, hardlink=True)
2177
self.addCleanup(target.unlock)
2178
self.assertEqual([], list(target.iter_changes(revision_tree)))
2179
self.assertTrue(source.is_executable('file1-id'))
2181
def install_rot13_content_filter(self, pattern):
2183
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2184
# below, but that looks a bit... hard to read even if it's exactly
2186
original_registry = filters._reset_registry()
2187
def restore_registry():
2188
filters._reset_registry(original_registry)
2189
self.addCleanup(restore_registry)
2190
def rot13(chunks, context=None):
2191
return [''.join(chunks).encode('rot13')]
2192
rot13filter = filters.ContentFilter(rot13, rot13)
2193
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
2194
os.mkdir(self.test_home_dir + '/.bazaar')
2195
rules_filename = self.test_home_dir + '/.bazaar/rules'
2196
f = open(rules_filename, 'wb')
2197
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2199
def uninstall_rules():
2200
os.remove(rules_filename)
2202
self.addCleanup(uninstall_rules)
2205
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2206
"""build_tree will not hardlink files that have content filtering rules
2207
applied to them (but will still hardlink other files from the same tree
2210
self.requireFeature(HardlinkFeature)
2211
self.install_rot13_content_filter('file1')
2212
source = self.create_ab_tree()
2213
target = self.make_branch_and_tree('target')
2214
revision_tree = source.basis_tree()
2215
revision_tree.lock_read()
2216
self.addCleanup(revision_tree.unlock)
2217
build_tree(revision_tree, target, source, hardlink=True)
2219
self.addCleanup(target.unlock)
2220
self.assertEqual([], list(target.iter_changes(revision_tree)))
2221
source_stat = os.stat('source/file1')
2222
target_stat = os.stat('target/file1')
2223
self.assertNotEqual(source_stat, target_stat)
2224
source_stat = os.stat('source/file2')
2225
target_stat = os.stat('target/file2')
2226
self.assertEqualStat(source_stat, target_stat)
2228
def test_case_insensitive_build_tree_inventory(self):
2229
if (features.CaseInsensitiveFilesystemFeature.available()
2230
or features.CaseInsCasePresFilenameFeature.available()):
2231
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2232
source = self.make_branch_and_tree('source')
2233
self.build_tree(['source/file', 'source/FILE'])
2234
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2235
source.commit('added files')
2236
# Don't try this at home, kids!
2237
# Force the tree to report that it is case insensitive
2238
target = self.make_branch_and_tree('target')
2239
target.case_sensitive = False
2240
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2241
self.assertEqual('file.moved', target.id2path('lower-id'))
2242
self.assertEqual('FILE', target.id2path('upper-id'))
2244
def test_build_tree_observes_sha(self):
2245
source = self.make_branch_and_tree('source')
2246
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2247
source.add(['file1', 'dir', 'dir/file2'],
2248
['file1-id', 'dir-id', 'file2-id'])
2249
source.commit('new files')
2250
target = self.make_branch_and_tree('target')
2252
self.addCleanup(target.unlock)
2253
# We make use of the fact that DirState caches its cutoff time. So we
2254
# set the 'safe' time to one minute in the future.
2255
state = target.current_dirstate()
2256
state._cutoff_time = time.time() + 60
2257
build_tree(source.basis_tree(), target)
2258
entry1_sha = osutils.sha_file_by_name('source/file1')
2259
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2260
# entry[1] is the state information, entry[1][0] is the state of the
2261
# working tree, entry[1][0][1] is the sha value for the current working
2263
entry1 = state._get_entry(0, path_utf8='file1')
2264
self.assertEqual(entry1_sha, entry1[1][0][1])
2265
# The 'size' field must also be set.
2266
self.assertEqual(25, entry1[1][0][2])
2267
entry1_state = entry1[1][0]
2268
entry2 = state._get_entry(0, path_utf8='dir/file2')
2269
self.assertEqual(entry2_sha, entry2[1][0][1])
2270
self.assertEqual(29, entry2[1][0][2])
2271
entry2_state = entry2[1][0]
2272
# Now, make sure that we don't have to re-read the content. The
2273
# packed_stat should match exactly.
2274
self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
2275
self.assertEqual(entry2_sha,
2276
target.get_file_sha1('file2-id', 'dir/file2'))
2277
self.assertEqual(entry1_state, entry1[1][0])
2278
self.assertEqual(entry2_state, entry2[1][0])
2281
class TestCommitTransform(tests.TestCaseWithTransport):
2283
def get_branch(self):
2284
tree = self.make_branch_and_tree('tree')
2286
self.addCleanup(tree.unlock)
2287
tree.commit('empty commit')
2290
def get_branch_and_transform(self):
2291
branch = self.get_branch()
2292
tt = TransformPreview(branch.basis_tree())
2293
self.addCleanup(tt.finalize)
2296
def test_commit_wrong_basis(self):
2297
branch = self.get_branch()
2298
basis = branch.repository.revision_tree(
2299
_mod_revision.NULL_REVISION)
2300
tt = TransformPreview(basis)
2301
self.addCleanup(tt.finalize)
2302
e = self.assertRaises(ValueError, tt.commit, branch, '')
2303
self.assertEqual('TreeTransform not based on branch basis: null:',
2306
def test_empy_commit(self):
2307
branch, tt = self.get_branch_and_transform()
2308
rev = tt.commit(branch, 'my message')
2309
self.assertEqual(2, branch.revno())
2310
repo = branch.repository
2311
self.assertEqual('my message', repo.get_revision(rev).message)
2313
def test_merge_parents(self):
2314
branch, tt = self.get_branch_and_transform()
2315
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2316
self.assertEqual(['rev1b', 'rev1c'],
2317
branch.basis_tree().get_parent_ids()[1:])
2319
def test_first_commit(self):
2320
branch = self.make_branch('branch')
2322
self.addCleanup(branch.unlock)
2323
tt = TransformPreview(branch.basis_tree())
2324
self.addCleanup(tt.finalize)
2325
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2326
rev = tt.commit(branch, 'my message')
2327
self.assertEqual([], branch.basis_tree().get_parent_ids())
2328
self.assertNotEqual(_mod_revision.NULL_REVISION,
2329
branch.last_revision())
2331
def test_first_commit_with_merge_parents(self):
2332
branch = self.make_branch('branch')
2334
self.addCleanup(branch.unlock)
2335
tt = TransformPreview(branch.basis_tree())
2336
self.addCleanup(tt.finalize)
2337
e = self.assertRaises(ValueError, tt.commit, branch,
2338
'my message', ['rev1b-id'])
2339
self.assertEqual('Cannot supply merge parents for first commit.',
2341
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2343
def test_add_files(self):
2344
branch, tt = self.get_branch_and_transform()
2345
tt.new_file('file', tt.root, 'contents', 'file-id')
2346
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2347
if SymlinkFeature.available():
2348
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2349
rev = tt.commit(branch, 'message')
2350
tree = branch.basis_tree()
2351
self.assertEqual('file', tree.id2path('file-id'))
2352
self.assertEqual('contents', tree.get_file_text('file-id'))
2353
self.assertEqual('dir', tree.id2path('dir-id'))
2354
if SymlinkFeature.available():
2355
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2356
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2358
def test_add_unversioned(self):
2359
branch, tt = self.get_branch_and_transform()
2360
tt.new_file('file', tt.root, 'contents')
2361
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2362
'message', strict=True)
2364
def test_modify_strict(self):
2365
branch, tt = self.get_branch_and_transform()
2366
tt.new_file('file', tt.root, 'contents', 'file-id')
2367
tt.commit(branch, 'message', strict=True)
2368
tt = TransformPreview(branch.basis_tree())
2369
self.addCleanup(tt.finalize)
2370
trans_id = tt.trans_id_file_id('file-id')
2371
tt.delete_contents(trans_id)
2372
tt.create_file('contents', trans_id)
2373
tt.commit(branch, 'message', strict=True)
2375
def test_commit_malformed(self):
2376
"""Committing a malformed transform should raise an exception.
2378
In this case, we are adding a file without adding its parent.
2380
branch, tt = self.get_branch_and_transform()
2381
parent_id = tt.trans_id_file_id('parent-id')
2382
tt.new_file('file', parent_id, 'contents', 'file-id')
2383
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2386
def test_commit_rich_revision_data(self):
2387
branch, tt = self.get_branch_and_transform()
2388
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2389
committer='me <me@example.com>',
2390
revprops={'foo': 'bar'}, revision_id='revid-1',
2391
authors=['Author1 <author1@example.com>',
2392
'Author2 <author2@example.com>',
2394
self.assertEqual('revid-1', rev_id)
2395
revision = branch.repository.get_revision(rev_id)
2396
self.assertEqual(1, revision.timestamp)
2397
self.assertEqual(43201, revision.timezone)
2398
self.assertEqual('me <me@example.com>', revision.committer)
2399
self.assertEqual(['Author1 <author1@example.com>',
2400
'Author2 <author2@example.com>'],
2401
revision.get_apparent_authors())
2402
del revision.properties['authors']
2403
self.assertEqual({'foo': 'bar',
2404
'branch-nick': 'tree'},
2405
revision.properties)
2407
def test_no_explicit_revprops(self):
2408
branch, tt = self.get_branch_and_transform()
2409
rev_id = tt.commit(branch, 'message', authors=[
2410
'Author1 <author1@example.com>',
2411
'Author2 <author2@example.com>', ])
2412
revision = branch.repository.get_revision(rev_id)
2413
self.assertEqual(['Author1 <author1@example.com>',
2414
'Author2 <author2@example.com>'],
2415
revision.get_apparent_authors())
2416
self.assertEqual('tree', revision.properties['branch-nick'])
2419
class TestBackupName(tests.TestCase):
2421
def test_deprecations(self):
2422
class MockTransform(object):
2424
def has_named_child(self, by_parent, parent_id, name):
2425
return name in by_parent.get(parent_id, [])
2427
class MockEntry(object):
2430
object.__init__(self)
732
class MockTransform(object):
734
def has_named_child(self, by_parent, parent_id, name):
735
for child_id in by_parent[parent_id]:
739
elif name == "name.~%s~" % child_id:
743
class MockEntry(object):
745
object.__init__(self)
748
class TestGetBackupName(TestCase):
749
def test_get_backup_name(self):
2433
750
tt = MockTransform()
2434
name1 = self.applyDeprecated(
2435
symbol_versioning.deprecated_in((2, 3, 0)),
2436
transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
2437
self.assertEqual('name.~1~', name1)
2438
name2 = self.applyDeprecated(
2439
symbol_versioning.deprecated_in((2, 3, 0)),
2440
transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
2441
self.assertEqual('name.~2~', name2)
2444
class TestFileMover(tests.TestCaseWithTransport):
2446
def test_file_mover(self):
2447
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2448
mover = _FileMover()
2449
mover.rename('a', 'q')
2450
self.assertPathExists('q')
2451
self.assertPathDoesNotExist('a')
2452
self.assertPathExists('q/b')
2453
self.assertPathExists('c')
2454
self.assertPathExists('c/d')
2456
def test_pre_delete_rollback(self):
2457
self.build_tree(['a/'])
2458
mover = _FileMover()
2459
mover.pre_delete('a', 'q')
2460
self.assertPathExists('q')
2461
self.assertPathDoesNotExist('a')
2463
self.assertPathDoesNotExist('q')
2464
self.assertPathExists('a')
2466
def test_apply_deletions(self):
2467
self.build_tree(['a/', 'b/'])
2468
mover = _FileMover()
2469
mover.pre_delete('a', 'q')
2470
mover.pre_delete('b', 'r')
2471
self.assertPathExists('q')
2472
self.assertPathExists('r')
2473
self.assertPathDoesNotExist('a')
2474
self.assertPathDoesNotExist('b')
2475
mover.apply_deletions()
2476
self.assertPathDoesNotExist('q')
2477
self.assertPathDoesNotExist('r')
2478
self.assertPathDoesNotExist('a')
2479
self.assertPathDoesNotExist('b')
2481
def test_file_mover_rollback(self):
2482
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2483
mover = _FileMover()
2484
mover.rename('c/d', 'c/f')
2485
mover.rename('c/e', 'c/d')
2487
mover.rename('a', 'c')
2488
except errors.FileExists, e:
2490
self.assertPathExists('a')
2491
self.assertPathExists('c/d')
2494
class Bogus(Exception):
2498
class TestTransformRollback(tests.TestCaseWithTransport):
2500
class ExceptionFileMover(_FileMover):
2502
def __init__(self, bad_source=None, bad_target=None):
2503
_FileMover.__init__(self)
2504
self.bad_source = bad_source
2505
self.bad_target = bad_target
2507
def rename(self, source, target):
2508
if (self.bad_source is not None and
2509
source.endswith(self.bad_source)):
2511
elif (self.bad_target is not None and
2512
target.endswith(self.bad_target)):
2515
_FileMover.rename(self, source, target)
2517
def test_rollback_rename(self):
2518
tree = self.make_branch_and_tree('.')
2519
self.build_tree(['a/', 'a/b'])
2520
tt = TreeTransform(tree)
2521
self.addCleanup(tt.finalize)
2522
a_id = tt.trans_id_tree_path('a')
2523
tt.adjust_path('c', tt.root, a_id)
2524
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2525
self.assertRaises(Bogus, tt.apply,
2526
_mover=self.ExceptionFileMover(bad_source='a'))
2527
self.assertPathExists('a')
2528
self.assertPathExists('a/b')
2530
self.assertPathExists('c')
2531
self.assertPathExists('c/d')
2533
def test_rollback_rename_into_place(self):
2534
tree = self.make_branch_and_tree('.')
2535
self.build_tree(['a/', 'a/b'])
2536
tt = TreeTransform(tree)
2537
self.addCleanup(tt.finalize)
2538
a_id = tt.trans_id_tree_path('a')
2539
tt.adjust_path('c', tt.root, a_id)
2540
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2541
self.assertRaises(Bogus, tt.apply,
2542
_mover=self.ExceptionFileMover(bad_target='c/d'))
2543
self.assertPathExists('a')
2544
self.assertPathExists('a/b')
2546
self.assertPathExists('c')
2547
self.assertPathExists('c/d')
2549
def test_rollback_deletion(self):
2550
tree = self.make_branch_and_tree('.')
2551
self.build_tree(['a/', 'a/b'])
2552
tt = TreeTransform(tree)
2553
self.addCleanup(tt.finalize)
2554
a_id = tt.trans_id_tree_path('a')
2555
tt.delete_contents(a_id)
2556
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2557
self.assertRaises(Bogus, tt.apply,
2558
_mover=self.ExceptionFileMover(bad_target='d'))
2559
self.assertPathExists('a')
2560
self.assertPathExists('a/b')
2563
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2564
"""Ensure treetransform creation errors can be safely cleaned up after"""
2566
def _override_globals_in_method(self, instance, method_name, globals):
2567
"""Replace method on instance with one with updated globals"""
2569
func = getattr(instance, method_name).im_func
2570
new_globals = dict(func.func_globals)
2571
new_globals.update(globals)
2572
new_func = types.FunctionType(func.func_code, new_globals,
2573
func.func_name, func.func_defaults)
2574
setattr(instance, method_name,
2575
types.MethodType(new_func, instance, instance.__class__))
2576
self.addCleanup(delattr, instance, method_name)
2579
def _fake_open_raises_before(name, mode):
2580
"""Like open() but raises before doing anything"""
2584
def _fake_open_raises_after(name, mode):
2585
"""Like open() but raises after creating file without returning"""
2586
open(name, mode).close()
2589
def create_transform_and_root_trans_id(self):
2590
"""Setup a transform creating a file in limbo"""
2591
tree = self.make_branch_and_tree('.')
2592
tt = TreeTransform(tree)
2593
return tt, tt.create_path("a", tt.root)
2595
def create_transform_and_subdir_trans_id(self):
2596
"""Setup a transform creating a directory containing a file in limbo"""
2597
tree = self.make_branch_and_tree('.')
2598
tt = TreeTransform(tree)
2599
d_trans_id = tt.create_path("d", tt.root)
2600
tt.create_directory(d_trans_id)
2601
f_trans_id = tt.create_path("a", d_trans_id)
2602
tt.adjust_path("a", d_trans_id, f_trans_id)
2603
return tt, f_trans_id
2605
def test_root_create_file_open_raises_before_creation(self):
2606
tt, trans_id = self.create_transform_and_root_trans_id()
2607
self._override_globals_in_method(tt, "create_file",
2608
{"open": self._fake_open_raises_before})
2609
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2610
path = tt._limbo_name(trans_id)
2611
self.assertPathDoesNotExist(path)
2613
self.assertPathDoesNotExist(tt._limbodir)
2615
def test_root_create_file_open_raises_after_creation(self):
2616
tt, trans_id = self.create_transform_and_root_trans_id()
2617
self._override_globals_in_method(tt, "create_file",
2618
{"open": self._fake_open_raises_after})
2619
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2620
path = tt._limbo_name(trans_id)
2621
self.assertPathExists(path)
2623
self.assertPathDoesNotExist(path)
2624
self.assertPathDoesNotExist(tt._limbodir)
2626
def test_subdir_create_file_open_raises_before_creation(self):
2627
tt, trans_id = self.create_transform_and_subdir_trans_id()
2628
self._override_globals_in_method(tt, "create_file",
2629
{"open": self._fake_open_raises_before})
2630
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2631
path = tt._limbo_name(trans_id)
2632
self.assertPathDoesNotExist(path)
2634
self.assertPathDoesNotExist(tt._limbodir)
2636
def test_subdir_create_file_open_raises_after_creation(self):
2637
tt, trans_id = self.create_transform_and_subdir_trans_id()
2638
self._override_globals_in_method(tt, "create_file",
2639
{"open": self._fake_open_raises_after})
2640
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2641
path = tt._limbo_name(trans_id)
2642
self.assertPathExists(path)
2644
self.assertPathDoesNotExist(path)
2645
self.assertPathDoesNotExist(tt._limbodir)
2647
def test_rename_in_limbo_rename_raises_after_rename(self):
2648
tt, trans_id = self.create_transform_and_root_trans_id()
2649
parent1 = tt.new_directory('parent1', tt.root)
2650
child1 = tt.new_file('child1', parent1, 'contents')
2651
parent2 = tt.new_directory('parent2', tt.root)
2653
class FakeOSModule(object):
2654
def rename(self, old, new):
2657
self._override_globals_in_method(tt, "_rename_in_limbo",
2658
{"os": FakeOSModule()})
2660
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2661
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2662
self.assertPathExists(path)
2664
self.assertPathDoesNotExist(path)
2665
self.assertPathDoesNotExist(tt._limbodir)
2667
def test_rename_in_limbo_rename_raises_before_rename(self):
2668
tt, trans_id = self.create_transform_and_root_trans_id()
2669
parent1 = tt.new_directory('parent1', tt.root)
2670
child1 = tt.new_file('child1', parent1, 'contents')
2671
parent2 = tt.new_directory('parent2', tt.root)
2673
class FakeOSModule(object):
2674
def rename(self, old, new):
2676
self._override_globals_in_method(tt, "_rename_in_limbo",
2677
{"os": FakeOSModule()})
2679
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2680
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2681
self.assertPathExists(path)
2683
self.assertPathDoesNotExist(path)
2684
self.assertPathDoesNotExist(tt._limbodir)
2687
class TestTransformMissingParent(tests.TestCaseWithTransport):
2689
def make_tt_with_versioned_dir(self):
2690
wt = self.make_branch_and_tree('.')
2691
self.build_tree(['dir/',])
2692
wt.add(['dir'], ['dir-id'])
2693
wt.commit('Create dir')
2694
tt = TreeTransform(wt)
2695
self.addCleanup(tt.finalize)
2698
def test_resolve_create_parent_for_versioned_file(self):
2699
wt, tt = self.make_tt_with_versioned_dir()
2700
dir_tid = tt.trans_id_tree_file_id('dir-id')
2701
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2702
tt.delete_contents(dir_tid)
2703
tt.unversion_file(dir_tid)
2704
conflicts = resolve_conflicts(tt)
2705
# one conflict for the missing directory, one for the unversioned
2707
self.assertLength(2, conflicts)
2709
def test_non_versioned_file_create_conflict(self):
2710
wt, tt = self.make_tt_with_versioned_dir()
2711
dir_tid = tt.trans_id_tree_file_id('dir-id')
2712
tt.new_file('file', dir_tid, 'Contents')
2713
tt.delete_contents(dir_tid)
2714
tt.unversion_file(dir_tid)
2715
conflicts = resolve_conflicts(tt)
2716
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2717
self.assertLength(1, conflicts)
2718
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2722
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2723
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2725
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2726
('', ''), ('directory', 'directory'), (False, False))
2729
class TestTransformPreview(tests.TestCaseWithTransport):
2731
def create_tree(self):
2732
tree = self.make_branch_and_tree('.')
2733
self.build_tree_contents([('a', 'content 1')])
2734
tree.set_root_id('TREE_ROOT')
2735
tree.add('a', 'a-id')
2736
tree.commit('rev1', rev_id='rev1')
2737
return tree.branch.repository.revision_tree('rev1')
2739
def get_empty_preview(self):
2740
repository = self.make_repository('repo')
2741
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2742
preview = TransformPreview(tree)
2743
self.addCleanup(preview.finalize)
2746
def test_transform_preview(self):
2747
revision_tree = self.create_tree()
2748
preview = TransformPreview(revision_tree)
2749
self.addCleanup(preview.finalize)
2751
def test_transform_preview_tree(self):
2752
revision_tree = self.create_tree()
2753
preview = TransformPreview(revision_tree)
2754
self.addCleanup(preview.finalize)
2755
preview.get_preview_tree()
2757
def test_transform_new_file(self):
2758
revision_tree = self.create_tree()
2759
preview = TransformPreview(revision_tree)
2760
self.addCleanup(preview.finalize)
2761
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2762
preview_tree = preview.get_preview_tree()
2763
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2765
preview_tree.get_file('file2-id').read(), 'content B\n')
2767
def test_diff_preview_tree(self):
2768
revision_tree = self.create_tree()
2769
preview = TransformPreview(revision_tree)
2770
self.addCleanup(preview.finalize)
2771
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2772
preview_tree = preview.get_preview_tree()
2774
show_diff_trees(revision_tree, preview_tree, out)
2775
lines = out.getvalue().splitlines()
2776
self.assertEqual(lines[0], "=== added file 'file2'")
2777
# 3 lines of diff administrivia
2778
self.assertEqual(lines[4], "+content B")
2780
def test_transform_conflicts(self):
2781
revision_tree = self.create_tree()
2782
preview = TransformPreview(revision_tree)
2783
self.addCleanup(preview.finalize)
2784
preview.new_file('a', preview.root, 'content 2')
2785
resolve_conflicts(preview)
2786
trans_id = preview.trans_id_file_id('a-id')
2787
self.assertEqual('a.moved', preview.final_name(trans_id))
2789
def get_tree_and_preview_tree(self):
2790
revision_tree = self.create_tree()
2791
preview = TransformPreview(revision_tree)
2792
self.addCleanup(preview.finalize)
2793
a_trans_id = preview.trans_id_file_id('a-id')
2794
preview.delete_contents(a_trans_id)
2795
preview.create_file('b content', a_trans_id)
2796
preview_tree = preview.get_preview_tree()
2797
return revision_tree, preview_tree
2799
def test_iter_changes(self):
2800
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2801
root = revision_tree.inventory.root.file_id
2802
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2803
(root, root), ('a', 'a'), ('file', 'file'),
2805
list(preview_tree.iter_changes(revision_tree)))
2807
def test_include_unchanged_succeeds(self):
2808
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2809
changes = preview_tree.iter_changes(revision_tree,
2810
include_unchanged=True)
2811
root = revision_tree.inventory.root.file_id
2813
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2815
def test_specific_files(self):
2816
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2817
changes = preview_tree.iter_changes(revision_tree,
2818
specific_files=[''])
2819
self.assertEqual([A_ENTRY], list(changes))
2821
def test_want_unversioned(self):
2822
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2823
changes = preview_tree.iter_changes(revision_tree,
2824
want_unversioned=True)
2825
self.assertEqual([A_ENTRY], list(changes))
2827
def test_ignore_extra_trees_no_specific_files(self):
2828
# extra_trees is harmless without specific_files, so we'll silently
2829
# accept it, even though we won't use it.
2830
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2831
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2833
def test_ignore_require_versioned_no_specific_files(self):
2834
# require_versioned is meaningless without specific_files.
2835
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2836
preview_tree.iter_changes(revision_tree, require_versioned=False)
2838
def test_ignore_pb(self):
2839
# pb could be supported, but TT.iter_changes doesn't support it.
2840
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2841
preview_tree.iter_changes(revision_tree)
2843
def test_kind(self):
2844
revision_tree = self.create_tree()
2845
preview = TransformPreview(revision_tree)
2846
self.addCleanup(preview.finalize)
2847
preview.new_file('file', preview.root, 'contents', 'file-id')
2848
preview.new_directory('directory', preview.root, 'dir-id')
2849
preview_tree = preview.get_preview_tree()
2850
self.assertEqual('file', preview_tree.kind('file-id'))
2851
self.assertEqual('directory', preview_tree.kind('dir-id'))
2853
def test_get_file_mtime(self):
2854
preview = self.get_empty_preview()
2855
file_trans_id = preview.new_file('file', preview.root, 'contents',
2857
limbo_path = preview._limbo_name(file_trans_id)
2858
preview_tree = preview.get_preview_tree()
2859
self.assertEqual(os.stat(limbo_path).st_mtime,
2860
preview_tree.get_file_mtime('file-id'))
2862
def test_get_file_mtime_renamed(self):
2863
work_tree = self.make_branch_and_tree('tree')
2864
self.build_tree(['tree/file'])
2865
work_tree.add('file', 'file-id')
2866
preview = TransformPreview(work_tree)
2867
self.addCleanup(preview.finalize)
2868
file_trans_id = preview.trans_id_tree_file_id('file-id')
2869
preview.adjust_path('renamed', preview.root, file_trans_id)
2870
preview_tree = preview.get_preview_tree()
2871
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2872
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2874
def test_get_file_size(self):
2875
work_tree = self.make_branch_and_tree('tree')
2876
self.build_tree_contents([('tree/old', 'old')])
2877
work_tree.add('old', 'old-id')
2878
preview = TransformPreview(work_tree)
2879
self.addCleanup(preview.finalize)
2880
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2882
tree = preview.get_preview_tree()
2883
self.assertEqual(len('old'), tree.get_file_size('old-id'))
2884
self.assertEqual(len('contents'), tree.get_file_size('new-id'))
2886
def test_get_file(self):
2887
preview = self.get_empty_preview()
2888
preview.new_file('file', preview.root, 'contents', 'file-id')
2889
preview_tree = preview.get_preview_tree()
2890
tree_file = preview_tree.get_file('file-id')
2892
self.assertEqual('contents', tree_file.read())
2896
def test_get_symlink_target(self):
2897
self.requireFeature(SymlinkFeature)
2898
preview = self.get_empty_preview()
2899
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2900
preview_tree = preview.get_preview_tree()
2901
self.assertEqual('target',
2902
preview_tree.get_symlink_target('symlink-id'))
2904
def test_all_file_ids(self):
2905
tree = self.make_branch_and_tree('tree')
2906
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2907
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2908
preview = TransformPreview(tree)
2909
self.addCleanup(preview.finalize)
2910
preview.unversion_file(preview.trans_id_file_id('b-id'))
2911
c_trans_id = preview.trans_id_file_id('c-id')
2912
preview.unversion_file(c_trans_id)
2913
preview.version_file('c-id', c_trans_id)
2914
preview_tree = preview.get_preview_tree()
2915
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2916
preview_tree.all_file_ids())
2918
def test_path2id_deleted_unchanged(self):
2919
tree = self.make_branch_and_tree('tree')
2920
self.build_tree(['tree/unchanged', 'tree/deleted'])
2921
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2922
preview = TransformPreview(tree)
2923
self.addCleanup(preview.finalize)
2924
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2925
preview_tree = preview.get_preview_tree()
2926
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2927
self.assertIs(None, preview_tree.path2id('deleted'))
2929
def test_path2id_created(self):
2930
tree = self.make_branch_and_tree('tree')
2931
self.build_tree(['tree/unchanged'])
2932
tree.add(['unchanged'], ['unchanged-id'])
2933
preview = TransformPreview(tree)
2934
self.addCleanup(preview.finalize)
2935
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2936
'contents', 'new-id')
2937
preview_tree = preview.get_preview_tree()
2938
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2940
def test_path2id_moved(self):
2941
tree = self.make_branch_and_tree('tree')
2942
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2943
tree.add(['old_parent', 'old_parent/child'],
2944
['old_parent-id', 'child-id'])
2945
preview = TransformPreview(tree)
2946
self.addCleanup(preview.finalize)
2947
new_parent = preview.new_directory('new_parent', preview.root,
2949
preview.adjust_path('child', new_parent,
2950
preview.trans_id_file_id('child-id'))
2951
preview_tree = preview.get_preview_tree()
2952
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2953
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2955
def test_path2id_renamed_parent(self):
2956
tree = self.make_branch_and_tree('tree')
2957
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2958
tree.add(['old_name', 'old_name/child'],
2959
['parent-id', 'child-id'])
2960
preview = TransformPreview(tree)
2961
self.addCleanup(preview.finalize)
2962
preview.adjust_path('new_name', preview.root,
2963
preview.trans_id_file_id('parent-id'))
2964
preview_tree = preview.get_preview_tree()
2965
self.assertIs(None, preview_tree.path2id('old_name/child'))
2966
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2968
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2969
preview_tree = tt.get_preview_tree()
2970
preview_result = list(preview_tree.iter_entries_by_dir(
2974
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2975
self.assertEqual(actual_result, preview_result)
2977
def test_iter_entries_by_dir_new(self):
2978
tree = self.make_branch_and_tree('tree')
2979
tt = TreeTransform(tree)
2980
tt.new_file('new', tt.root, 'contents', 'new-id')
2981
self.assertMatchingIterEntries(tt)
2983
def test_iter_entries_by_dir_deleted(self):
2984
tree = self.make_branch_and_tree('tree')
2985
self.build_tree(['tree/deleted'])
2986
tree.add('deleted', 'deleted-id')
2987
tt = TreeTransform(tree)
2988
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2989
self.assertMatchingIterEntries(tt)
2991
def test_iter_entries_by_dir_unversioned(self):
2992
tree = self.make_branch_and_tree('tree')
2993
self.build_tree(['tree/removed'])
2994
tree.add('removed', 'removed-id')
2995
tt = TreeTransform(tree)
2996
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2997
self.assertMatchingIterEntries(tt)
2999
def test_iter_entries_by_dir_moved(self):
3000
tree = self.make_branch_and_tree('tree')
3001
self.build_tree(['tree/moved', 'tree/new_parent/'])
3002
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
3003
tt = TreeTransform(tree)
3004
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
3005
tt.trans_id_file_id('moved-id'))
3006
self.assertMatchingIterEntries(tt)
3008
def test_iter_entries_by_dir_specific_file_ids(self):
3009
tree = self.make_branch_and_tree('tree')
3010
tree.set_root_id('tree-root-id')
3011
self.build_tree(['tree/parent/', 'tree/parent/child'])
3012
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
3013
tt = TreeTransform(tree)
3014
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
3016
def test_symlink_content_summary(self):
3017
self.requireFeature(SymlinkFeature)
3018
preview = self.get_empty_preview()
3019
preview.new_symlink('path', preview.root, 'target', 'path-id')
3020
summary = preview.get_preview_tree().path_content_summary('path')
3021
self.assertEqual(('symlink', None, None, 'target'), summary)
3023
def test_missing_content_summary(self):
3024
preview = self.get_empty_preview()
3025
summary = preview.get_preview_tree().path_content_summary('path')
3026
self.assertEqual(('missing', None, None, None), summary)
3028
def test_deleted_content_summary(self):
3029
tree = self.make_branch_and_tree('tree')
3030
self.build_tree(['tree/path/'])
3032
preview = TransformPreview(tree)
3033
self.addCleanup(preview.finalize)
3034
preview.delete_contents(preview.trans_id_tree_path('path'))
3035
summary = preview.get_preview_tree().path_content_summary('path')
3036
self.assertEqual(('missing', None, None, None), summary)
3038
def test_file_content_summary_executable(self):
3039
preview = self.get_empty_preview()
3040
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
3041
preview.set_executability(True, path_id)
3042
summary = preview.get_preview_tree().path_content_summary('path')
3043
self.assertEqual(4, len(summary))
3044
self.assertEqual('file', summary[0])
3045
# size must be known
3046
self.assertEqual(len('contents'), summary[1])
3048
self.assertEqual(True, summary[2])
3049
# will not have hash (not cheap to determine)
3050
self.assertIs(None, summary[3])
3052
def test_change_executability(self):
3053
tree = self.make_branch_and_tree('tree')
3054
self.build_tree(['tree/path'])
3056
preview = TransformPreview(tree)
3057
self.addCleanup(preview.finalize)
3058
path_id = preview.trans_id_tree_path('path')
3059
preview.set_executability(True, path_id)
3060
summary = preview.get_preview_tree().path_content_summary('path')
3061
self.assertEqual(True, summary[2])
3063
def test_file_content_summary_non_exec(self):
3064
preview = self.get_empty_preview()
3065
preview.new_file('path', preview.root, 'contents', 'path-id')
3066
summary = preview.get_preview_tree().path_content_summary('path')
3067
self.assertEqual(4, len(summary))
3068
self.assertEqual('file', summary[0])
3069
# size must be known
3070
self.assertEqual(len('contents'), summary[1])
3072
self.assertEqual(False, summary[2])
3073
# will not have hash (not cheap to determine)
3074
self.assertIs(None, summary[3])
3076
def test_dir_content_summary(self):
3077
preview = self.get_empty_preview()
3078
preview.new_directory('path', preview.root, 'path-id')
3079
summary = preview.get_preview_tree().path_content_summary('path')
3080
self.assertEqual(('directory', None, None, None), summary)
3082
def test_tree_content_summary(self):
3083
preview = self.get_empty_preview()
3084
path = preview.new_directory('path', preview.root, 'path-id')
3085
preview.set_tree_reference('rev-1', path)
3086
summary = preview.get_preview_tree().path_content_summary('path')
3087
self.assertEqual(4, len(summary))
3088
self.assertEqual('tree-reference', summary[0])
3090
def test_annotate(self):
3091
tree = self.make_branch_and_tree('tree')
3092
self.build_tree_contents([('tree/file', 'a\n')])
3093
tree.add('file', 'file-id')
3094
tree.commit('a', rev_id='one')
3095
self.build_tree_contents([('tree/file', 'a\nb\n')])
3096
preview = TransformPreview(tree)
3097
self.addCleanup(preview.finalize)
3098
file_trans_id = preview.trans_id_file_id('file-id')
3099
preview.delete_contents(file_trans_id)
3100
preview.create_file('a\nb\nc\n', file_trans_id)
3101
preview_tree = preview.get_preview_tree()
3107
annotation = preview_tree.annotate_iter('file-id', 'me:')
3108
self.assertEqual(expected, annotation)
3110
def test_annotate_missing(self):
3111
preview = self.get_empty_preview()
3112
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3113
preview_tree = preview.get_preview_tree()
3119
annotation = preview_tree.annotate_iter('file-id', 'me:')
3120
self.assertEqual(expected, annotation)
3122
def test_annotate_rename(self):
3123
tree = self.make_branch_and_tree('tree')
3124
self.build_tree_contents([('tree/file', 'a\n')])
3125
tree.add('file', 'file-id')
3126
tree.commit('a', rev_id='one')
3127
preview = TransformPreview(tree)
3128
self.addCleanup(preview.finalize)
3129
file_trans_id = preview.trans_id_file_id('file-id')
3130
preview.adjust_path('newname', preview.root, file_trans_id)
3131
preview_tree = preview.get_preview_tree()
3135
annotation = preview_tree.annotate_iter('file-id', 'me:')
3136
self.assertEqual(expected, annotation)
3138
def test_annotate_deleted(self):
3139
tree = self.make_branch_and_tree('tree')
3140
self.build_tree_contents([('tree/file', 'a\n')])
3141
tree.add('file', 'file-id')
3142
tree.commit('a', rev_id='one')
3143
self.build_tree_contents([('tree/file', 'a\nb\n')])
3144
preview = TransformPreview(tree)
3145
self.addCleanup(preview.finalize)
3146
file_trans_id = preview.trans_id_file_id('file-id')
3147
preview.delete_contents(file_trans_id)
3148
preview_tree = preview.get_preview_tree()
3149
annotation = preview_tree.annotate_iter('file-id', 'me:')
3150
self.assertIs(None, annotation)
3152
def test_stored_kind(self):
3153
preview = self.get_empty_preview()
3154
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3155
preview_tree = preview.get_preview_tree()
3156
self.assertEqual('file', preview_tree.stored_kind('file-id'))
3158
def test_is_executable(self):
3159
preview = self.get_empty_preview()
3160
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3161
preview.set_executability(True, preview.trans_id_file_id('file-id'))
3162
preview_tree = preview.get_preview_tree()
3163
self.assertEqual(True, preview_tree.is_executable('file-id'))
3165
def test_get_set_parent_ids(self):
3166
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3167
self.assertEqual([], preview_tree.get_parent_ids())
3168
preview_tree.set_parent_ids(['rev-1'])
3169
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
3171
def test_plan_file_merge(self):
3172
work_a = self.make_branch_and_tree('wta')
3173
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3174
work_a.add('file', 'file-id')
3175
base_id = work_a.commit('base version')
3176
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3177
preview = TransformPreview(work_a)
3178
self.addCleanup(preview.finalize)
3179
trans_id = preview.trans_id_file_id('file-id')
3180
preview.delete_contents(trans_id)
3181
preview.create_file('b\nc\nd\ne\n', trans_id)
3182
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3183
tree_a = preview.get_preview_tree()
3184
tree_a.set_parent_ids([base_id])
3186
('killed-a', 'a\n'),
3187
('killed-b', 'b\n'),
3188
('unchanged', 'c\n'),
3189
('unchanged', 'd\n'),
3192
], list(tree_a.plan_file_merge('file-id', tree_b)))
3194
def test_plan_file_merge_revision_tree(self):
3195
work_a = self.make_branch_and_tree('wta')
3196
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3197
work_a.add('file', 'file-id')
3198
base_id = work_a.commit('base version')
3199
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3200
preview = TransformPreview(work_a.basis_tree())
3201
self.addCleanup(preview.finalize)
3202
trans_id = preview.trans_id_file_id('file-id')
3203
preview.delete_contents(trans_id)
3204
preview.create_file('b\nc\nd\ne\n', trans_id)
3205
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3206
tree_a = preview.get_preview_tree()
3207
tree_a.set_parent_ids([base_id])
3209
('killed-a', 'a\n'),
3210
('killed-b', 'b\n'),
3211
('unchanged', 'c\n'),
3212
('unchanged', 'd\n'),
3215
], list(tree_a.plan_file_merge('file-id', tree_b)))
3217
def test_walkdirs(self):
3218
preview = self.get_empty_preview()
3219
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3220
# FIXME: new_directory should mark root.
3221
preview.fixup_new_roots()
3222
preview_tree = preview.get_preview_tree()
3223
file_trans_id = preview.new_file('a', preview.root, 'contents',
3225
expected = [(('', 'tree-root'),
3226
[('a', 'a', 'file', None, 'a-id', 'file')])]
3227
self.assertEqual(expected, list(preview_tree.walkdirs()))
3229
def test_extras(self):
3230
work_tree = self.make_branch_and_tree('tree')
3231
self.build_tree(['tree/removed-file', 'tree/existing-file',
3232
'tree/not-removed-file'])
3233
work_tree.add(['removed-file', 'not-removed-file'])
3234
preview = TransformPreview(work_tree)
3235
self.addCleanup(preview.finalize)
3236
preview.new_file('new-file', preview.root, 'contents')
3237
preview.new_file('new-versioned-file', preview.root, 'contents',
3239
tree = preview.get_preview_tree()
3240
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3241
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
3244
def test_merge_into_preview(self):
3245
work_tree = self.make_branch_and_tree('tree')
3246
self.build_tree_contents([('tree/file','b\n')])
3247
work_tree.add('file', 'file-id')
3248
work_tree.commit('first commit')
3249
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
3250
self.build_tree_contents([('child/file','b\nc\n')])
3251
child_tree.commit('child commit')
3252
child_tree.lock_write()
3253
self.addCleanup(child_tree.unlock)
3254
work_tree.lock_write()
3255
self.addCleanup(work_tree.unlock)
3256
preview = TransformPreview(work_tree)
3257
self.addCleanup(preview.finalize)
3258
file_trans_id = preview.trans_id_file_id('file-id')
3259
preview.delete_contents(file_trans_id)
3260
preview.create_file('a\nb\n', file_trans_id)
3261
preview_tree = preview.get_preview_tree()
3262
merger = Merger.from_revision_ids(None, preview_tree,
3263
child_tree.branch.last_revision(),
3264
other_branch=child_tree.branch,
3265
tree_branch=work_tree.branch)
3266
merger.merge_type = Merge3Merger
3267
tt = merger.make_merger().make_preview_transform()
3268
self.addCleanup(tt.finalize)
3269
final_tree = tt.get_preview_tree()
3270
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3272
def test_merge_preview_into_workingtree(self):
3273
tree = self.make_branch_and_tree('tree')
3274
tree.set_root_id('TREE_ROOT')
3275
tt = TransformPreview(tree)
3276
self.addCleanup(tt.finalize)
3277
tt.new_file('name', tt.root, 'content', 'file-id')
3278
tree2 = self.make_branch_and_tree('tree2')
3279
tree2.set_root_id('TREE_ROOT')
3280
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3281
None, tree.basis_tree())
3282
merger.merge_type = Merge3Merger
3285
def test_merge_preview_into_workingtree_handles_conflicts(self):
3286
tree = self.make_branch_and_tree('tree')
3287
self.build_tree_contents([('tree/foo', 'bar')])
3288
tree.add('foo', 'foo-id')
3290
tt = TransformPreview(tree)
3291
self.addCleanup(tt.finalize)
3292
trans_id = tt.trans_id_file_id('foo-id')
3293
tt.delete_contents(trans_id)
3294
tt.create_file('baz', trans_id)
3295
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
3296
self.build_tree_contents([('tree2/foo', 'qux')])
3298
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3299
pb, tree.basis_tree())
3300
merger.merge_type = Merge3Merger
3303
def test_has_filename(self):
3304
wt = self.make_branch_and_tree('tree')
3305
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3306
tt = TransformPreview(wt)
3307
removed_id = tt.trans_id_tree_path('removed')
3308
tt.delete_contents(removed_id)
3309
tt.new_file('new', tt.root, 'contents')
3310
modified_id = tt.trans_id_tree_path('modified')
3311
tt.delete_contents(modified_id)
3312
tt.create_file('modified-contents', modified_id)
3313
self.addCleanup(tt.finalize)
3314
tree = tt.get_preview_tree()
3315
self.assertTrue(tree.has_filename('unmodified'))
3316
self.assertFalse(tree.has_filename('not-present'))
3317
self.assertFalse(tree.has_filename('removed'))
3318
self.assertTrue(tree.has_filename('new'))
3319
self.assertTrue(tree.has_filename('modified'))
3321
def test_is_executable(self):
3322
tree = self.make_branch_and_tree('tree')
3323
preview = TransformPreview(tree)
3324
self.addCleanup(preview.finalize)
3325
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3326
preview_tree = preview.get_preview_tree()
3327
self.assertEqual(False, preview_tree.is_executable('baz-id',
3329
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3331
def test_commit_preview_tree(self):
3332
tree = self.make_branch_and_tree('tree')
3333
rev_id = tree.commit('rev1')
3334
tree.branch.lock_write()
3335
self.addCleanup(tree.branch.unlock)
3336
tt = TransformPreview(tree)
3337
tt.new_file('file', tt.root, 'contents', 'file_id')
3338
self.addCleanup(tt.finalize)
3339
preview = tt.get_preview_tree()
3340
preview.set_parent_ids([rev_id])
3341
builder = tree.branch.get_commit_builder([rev_id])
3342
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3343
builder.finish_inventory()
3344
rev2_id = builder.commit('rev2')
3345
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3346
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3348
def test_ascii_limbo_paths(self):
3349
self.requireFeature(features.UnicodeFilenameFeature)
3350
branch = self.make_branch('any')
3351
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3352
tt = TransformPreview(tree)
3353
self.addCleanup(tt.finalize)
3354
foo_id = tt.new_directory('', ROOT_PARENT)
3355
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3356
limbo_path = tt._limbo_name(bar_id)
3357
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3360
class FakeSerializer(object):
3361
"""Serializer implementation that simply returns the input.
3363
The input is returned in the order used by pack.ContainerPushParser.
3366
def bytes_record(bytes, names):
3370
class TestSerializeTransform(tests.TestCaseWithTransport):
3372
_test_needs_features = [features.UnicodeFilenameFeature]
3374
def get_preview(self, tree=None):
3376
tree = self.make_branch_and_tree('tree')
3377
tt = TransformPreview(tree)
3378
self.addCleanup(tt.finalize)
3381
def assertSerializesTo(self, expected, tt):
3382
records = list(tt.serialize(FakeSerializer()))
3383
self.assertEqual(expected, records)
3386
def default_attribs():
3391
'_new_executability': {},
3393
'_tree_path_ids': {'': 'new-0'},
3395
'_removed_contents': [],
3396
'_non_present_ids': {},
3399
def make_records(self, attribs, contents):
3401
(((('attribs'),),), bencode.bencode(attribs))]
3402
records.extend([(((n, k),), c) for n, k, c in contents])
3405
def creation_records(self):
3406
attribs = self.default_attribs()
3407
attribs['_id_number'] = 3
3408
attribs['_new_name'] = {
3409
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3410
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3411
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3412
attribs['_new_executability'] = {'new-1': 1}
3414
('new-1', 'file', 'i 1\nbar\n'),
3415
('new-2', 'directory', ''),
3417
return self.make_records(attribs, contents)
3419
def test_serialize_creation(self):
3420
tt = self.get_preview()
3421
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3422
tt.new_directory('qux', tt.root, 'quxx')
3423
self.assertSerializesTo(self.creation_records(), tt)
3425
def test_deserialize_creation(self):
3426
tt = self.get_preview()
3427
tt.deserialize(iter(self.creation_records()))
3428
self.assertEqual(3, tt._id_number)
3429
self.assertEqual({'new-1': u'foo\u1234',
3430
'new-2': 'qux'}, tt._new_name)
3431
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3432
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3433
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3434
self.assertEqual({'new-1': True}, tt._new_executability)
3435
self.assertEqual({'new-1': 'file',
3436
'new-2': 'directory'}, tt._new_contents)
3437
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3439
foo_content = foo_limbo.read()
3442
self.assertEqual('bar', foo_content)
3444
def symlink_creation_records(self):
3445
attribs = self.default_attribs()
3446
attribs['_id_number'] = 2
3447
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3448
attribs['_new_parent'] = {'new-1': 'new-0'}
3449
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3450
return self.make_records(attribs, contents)
3452
def test_serialize_symlink_creation(self):
3453
self.requireFeature(features.SymlinkFeature)
3454
tt = self.get_preview()
3455
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3456
self.assertSerializesTo(self.symlink_creation_records(), tt)
3458
def test_deserialize_symlink_creation(self):
3459
self.requireFeature(features.SymlinkFeature)
3460
tt = self.get_preview()
3461
tt.deserialize(iter(self.symlink_creation_records()))
3462
abspath = tt._limbo_name('new-1')
3463
foo_content = osutils.readlink(abspath)
3464
self.assertEqual(u'bar\u1234', foo_content)
3466
def make_destruction_preview(self):
3467
tree = self.make_branch_and_tree('.')
3468
self.build_tree([u'foo\u1234', 'bar'])
3469
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3470
return self.get_preview(tree)
3472
def destruction_records(self):
3473
attribs = self.default_attribs()
3474
attribs['_id_number'] = 3
3475
attribs['_removed_id'] = ['new-1']
3476
attribs['_removed_contents'] = ['new-2']
3477
attribs['_tree_path_ids'] = {
3479
u'foo\u1234'.encode('utf-8'): 'new-1',
3482
return self.make_records(attribs, [])
3484
def test_serialize_destruction(self):
3485
tt = self.make_destruction_preview()
3486
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3487
tt.unversion_file(foo_trans_id)
3488
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3489
tt.delete_contents(bar_trans_id)
3490
self.assertSerializesTo(self.destruction_records(), tt)
3492
def test_deserialize_destruction(self):
3493
tt = self.make_destruction_preview()
3494
tt.deserialize(iter(self.destruction_records()))
3495
self.assertEqual({u'foo\u1234': 'new-1',
3497
'': tt.root}, tt._tree_path_ids)
3498
self.assertEqual({'new-1': u'foo\u1234',
3500
tt.root: ''}, tt._tree_id_paths)
3501
self.assertEqual(set(['new-1']), tt._removed_id)
3502
self.assertEqual(set(['new-2']), tt._removed_contents)
3504
def missing_records(self):
3505
attribs = self.default_attribs()
3506
attribs['_id_number'] = 2
3507
attribs['_non_present_ids'] = {
3509
return self.make_records(attribs, [])
3511
def test_serialize_missing(self):
3512
tt = self.get_preview()
3513
boo_trans_id = tt.trans_id_file_id('boo')
3514
self.assertSerializesTo(self.missing_records(), tt)
3516
def test_deserialize_missing(self):
3517
tt = self.get_preview()
3518
tt.deserialize(iter(self.missing_records()))
3519
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3521
def make_modification_preview(self):
3522
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3523
LINES_TWO = 'z\nbb\nx\ndd\n'
3524
tree = self.make_branch_and_tree('tree')
3525
self.build_tree_contents([('tree/file', LINES_ONE)])
3526
tree.add('file', 'file-id')
3527
return self.get_preview(tree), LINES_TWO
3529
def modification_records(self):
3530
attribs = self.default_attribs()
3531
attribs['_id_number'] = 2
3532
attribs['_tree_path_ids'] = {
3535
attribs['_removed_contents'] = ['new-1']
3536
contents = [('new-1', 'file',
3537
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3538
return self.make_records(attribs, contents)
3540
def test_serialize_modification(self):
3541
tt, LINES = self.make_modification_preview()
3542
trans_id = tt.trans_id_file_id('file-id')
3543
tt.delete_contents(trans_id)
3544
tt.create_file(LINES, trans_id)
3545
self.assertSerializesTo(self.modification_records(), tt)
3547
def test_deserialize_modification(self):
3548
tt, LINES = self.make_modification_preview()
3549
tt.deserialize(iter(self.modification_records()))
3550
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3552
def make_kind_change_preview(self):
3553
LINES = 'a\nb\nc\nd\n'
3554
tree = self.make_branch_and_tree('tree')
3555
self.build_tree(['tree/foo/'])
3556
tree.add('foo', 'foo-id')
3557
return self.get_preview(tree), LINES
3559
def kind_change_records(self):
3560
attribs = self.default_attribs()
3561
attribs['_id_number'] = 2
3562
attribs['_tree_path_ids'] = {
3565
attribs['_removed_contents'] = ['new-1']
3566
contents = [('new-1', 'file',
3567
'i 4\na\nb\nc\nd\n\n')]
3568
return self.make_records(attribs, contents)
3570
def test_serialize_kind_change(self):
3571
tt, LINES = self.make_kind_change_preview()
3572
trans_id = tt.trans_id_file_id('foo-id')
3573
tt.delete_contents(trans_id)
3574
tt.create_file(LINES, trans_id)
3575
self.assertSerializesTo(self.kind_change_records(), tt)
3577
def test_deserialize_kind_change(self):
3578
tt, LINES = self.make_kind_change_preview()
3579
tt.deserialize(iter(self.kind_change_records()))
3580
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3582
def make_add_contents_preview(self):
3583
LINES = 'a\nb\nc\nd\n'
3584
tree = self.make_branch_and_tree('tree')
3585
self.build_tree(['tree/foo'])
3587
os.unlink('tree/foo')
3588
return self.get_preview(tree), LINES
3590
def add_contents_records(self):
3591
attribs = self.default_attribs()
3592
attribs['_id_number'] = 2
3593
attribs['_tree_path_ids'] = {
3596
contents = [('new-1', 'file',
3597
'i 4\na\nb\nc\nd\n\n')]
3598
return self.make_records(attribs, contents)
3600
def test_serialize_add_contents(self):
3601
tt, LINES = self.make_add_contents_preview()
3602
trans_id = tt.trans_id_tree_path('foo')
3603
tt.create_file(LINES, trans_id)
3604
self.assertSerializesTo(self.add_contents_records(), tt)
3606
def test_deserialize_add_contents(self):
3607
tt, LINES = self.make_add_contents_preview()
3608
tt.deserialize(iter(self.add_contents_records()))
3609
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3611
def test_get_parents_lines(self):
3612
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3613
LINES_TWO = 'z\nbb\nx\ndd\n'
3614
tree = self.make_branch_and_tree('tree')
3615
self.build_tree_contents([('tree/file', LINES_ONE)])
3616
tree.add('file', 'file-id')
3617
tt = self.get_preview(tree)
3618
trans_id = tt.trans_id_tree_path('file')
3619
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3620
tt._get_parents_lines(trans_id))
3622
def test_get_parents_texts(self):
3623
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3624
LINES_TWO = 'z\nbb\nx\ndd\n'
3625
tree = self.make_branch_and_tree('tree')
3626
self.build_tree_contents([('tree/file', LINES_ONE)])
3627
tree.add('file', 'file-id')
3628
tt = self.get_preview(tree)
3629
trans_id = tt.trans_id_tree_path('file')
3630
self.assertEqual((LINES_ONE,),
3631
tt._get_parents_texts(trans_id))
3634
class TestOrphan(tests.TestCaseWithTransport):
3636
def test_no_orphan_for_transform_preview(self):
3637
tree = self.make_branch_and_tree('tree')
3638
tt = transform.TransformPreview(tree)
3639
self.addCleanup(tt.finalize)
3640
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3642
def _set_orphan_policy(self, wt, policy):
3643
wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
3646
def _prepare_orphan(self, wt):
3647
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3648
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3649
wt.commit('add dir and file ignoring foo')
3650
tt = transform.TreeTransform(wt)
3651
self.addCleanup(tt.finalize)
3652
# dir and bar are deleted
3653
dir_tid = tt.trans_id_tree_path('dir')
3654
file_tid = tt.trans_id_tree_path('dir/file')
3655
orphan_tid = tt.trans_id_tree_path('dir/foo')
3656
tt.delete_contents(file_tid)
3657
tt.unversion_file(file_tid)
3658
tt.delete_contents(dir_tid)
3659
tt.unversion_file(dir_tid)
3660
# There should be a conflict because dir still contain foo
3661
raw_conflicts = tt.find_conflicts()
3662
self.assertLength(1, raw_conflicts)
3663
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3664
return tt, orphan_tid
3666
def test_new_orphan_created(self):
3667
wt = self.make_branch_and_tree('.')
3668
self._set_orphan_policy(wt, 'move')
3669
tt, orphan_tid = self._prepare_orphan(wt)
3672
warnings.append(args[0] % args[1:])
3673
self.overrideAttr(trace, 'warning', warning)
3674
remaining_conflicts = resolve_conflicts(tt)
3675
self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
3677
# Yeah for resolved conflicts !
3678
self.assertLength(0, remaining_conflicts)
3679
# We have a new orphan
3680
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3681
self.assertEquals('bzr-orphans',
3682
tt.final_name(tt.final_parent(orphan_tid)))
3684
def test_never_orphan(self):
3685
wt = self.make_branch_and_tree('.')
3686
self._set_orphan_policy(wt, 'conflict')
3687
tt, orphan_tid = self._prepare_orphan(wt)
3688
remaining_conflicts = resolve_conflicts(tt)
3689
self.assertLength(1, remaining_conflicts)
3690
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3691
remaining_conflicts.pop())
3693
def test_orphan_error(self):
3694
def bogus_orphan(tt, orphan_id, parent_id):
3695
raise transform.OrphaningError(tt.final_name(orphan_id),
3696
tt.final_name(parent_id))
3697
transform.orphaning_registry.register('bogus', bogus_orphan,
3698
'Raise an error when orphaning')
3699
wt = self.make_branch_and_tree('.')
3700
self._set_orphan_policy(wt, 'bogus')
3701
tt, orphan_tid = self._prepare_orphan(wt)
3702
remaining_conflicts = resolve_conflicts(tt)
3703
self.assertLength(1, remaining_conflicts)
3704
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3705
remaining_conflicts.pop())
3707
def test_unknown_orphan_policy(self):
3708
wt = self.make_branch_and_tree('.')
3709
# Set a fictional policy nobody ever implemented
3710
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3711
tt, orphan_tid = self._prepare_orphan(wt)
3714
warnings.append(args[0] % args[1:])
3715
self.overrideAttr(trace, 'warning', warning)
3716
remaining_conflicts = resolve_conflicts(tt)
3717
# We fallback to the default policy which create a conflict
3718
self.assertLength(1, remaining_conflicts)
3719
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3720
remaining_conflicts.pop())
3721
self.assertLength(1, warnings)
3722
self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')
751
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
752
self.assertEqual(name, 'name.~1~')
753
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
754
self.assertEqual(name, 'name.~2~')
755
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
756
self.assertEqual(name, 'name.~1~')
757
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
758
self.assertEqual(name, 'name.~1~')
759
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
760
self.assertEqual(name, 'name.~4~')