479
1005
rename.set_executability(True, myfile)
482
def test_find_interesting(self):
483
create, root = self.get_transform()
485
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
486
create.new_file('uvfile', root, 'othertext')
1008
def test_rename_fails(self):
1009
self.requireFeature(features.not_running_as_root)
1010
# see https://bugs.launchpad.net/bzr/+bug/491763
1011
create, root_id = self.get_transform()
1012
first_dir = create.new_directory('first-dir', root_id, 'first-id')
1013
myfile = create.new_file('myfile', root_id, 'myfile-text',
488
self.assertEqual(find_interesting(wt, wt, ['vfile']),
490
self.assertRaises(NotVersionedError, find_interesting, wt, wt,
1016
if os.name == "posix" and sys.platform != "cygwin":
1017
# posix filesystems fail on renaming if the readonly bit is set
1018
osutils.make_readonly(self.wt.abspath('first-dir'))
1019
elif os.name == "nt":
1020
# windows filesystems fail on renaming open files
1021
self.addCleanup(file(self.wt.abspath('myfile')).close)
1023
self.skip("Don't know how to force a permissions error on rename")
1024
# now transform to rename
1025
rename_transform, root_id = self.get_transform()
1026
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
1027
dir_id = rename_transform.trans_id_file_id('first-id')
1028
rename_transform.adjust_path('newname', dir_id, file_trans_id)
1029
e = self.assertRaises(errors.TransformRenameFailed,
1030
rename_transform.apply)
1031
# On nix looks like:
1032
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
1033
# to .../first-dir/newname: [Errno 13] Permission denied"
1034
# On windows looks like:
1035
# "Failed to rename .../work/myfile to
1036
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
1037
# This test isn't concerned with exactly what the error looks like,
1038
# and the strerror will vary across OS and locales, but the assert
1039
# that the exeception attributes are what we expect
1040
self.assertEqual(e.errno, errno.EACCES)
1041
if os.name == "posix":
1042
self.assertEndsWith(e.to_path, "/first-dir/newname")
1044
self.assertEqual(os.path.basename(e.from_path), "myfile")
1046
def test_set_executability_order(self):
1047
"""Ensure that executability behaves the same, no matter what order.
1049
- create file and set executability simultaneously
1050
- create file and set executability afterward
1051
- unsetting the executability of a file whose executability has not been
1052
declared should throw an exception (this may happen when a
1053
merge attempts to create a file with a duplicate ID)
1055
transform, root = self.get_transform()
1056
wt = transform._tree
1058
self.addCleanup(wt.unlock)
1059
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
1061
sac = transform.new_file('set_after_creation', root,
1062
'Set after creation', 'sac')
1063
transform.set_executability(True, sac)
1064
uws = transform.new_file('unset_without_set', root, 'Unset badly',
1066
self.assertRaises(KeyError, transform.set_executability, None, uws)
1068
self.assertTrue(wt.is_executable('soc'))
1069
self.assertTrue(wt.is_executable('sac'))
1071
def test_preserve_mode(self):
1072
"""File mode is preserved when replacing content"""
1073
if sys.platform == 'win32':
1074
raise TestSkipped('chmod has no effect on win32')
1075
transform, root = self.get_transform()
1076
transform.new_file('file1', root, 'contents', 'file1-id', True)
1078
self.wt.lock_write()
1079
self.addCleanup(self.wt.unlock)
1080
self.assertTrue(self.wt.is_executable('file1-id'))
1081
transform, root = self.get_transform()
1082
file1_id = transform.trans_id_tree_file_id('file1-id')
1083
transform.delete_contents(file1_id)
1084
transform.create_file('contents2', file1_id)
1086
self.assertTrue(self.wt.is_executable('file1-id'))
1088
def test__set_mode_stats_correctly(self):
1089
"""_set_mode stats to determine file mode."""
1090
if sys.platform == 'win32':
1091
raise TestSkipped('chmod has no effect on win32')
1095
def instrumented_stat(path):
1096
stat_paths.append(path)
1097
return real_stat(path)
1099
transform, root = self.get_transform()
1101
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
1102
file_id='bar-id-1', executable=False)
1105
transform, root = self.get_transform()
1106
bar1_id = transform.trans_id_tree_path('bar')
1107
bar2_id = transform.trans_id_tree_path('bar2')
1109
os.stat = instrumented_stat
1110
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
1113
transform.finalize()
1115
bar1_abspath = self.wt.abspath('bar')
1116
self.assertEqual([bar1_abspath], stat_paths)
1118
def test_iter_changes(self):
1119
self.wt.set_root_id('eert_toor')
1120
transform, root = self.get_transform()
1121
transform.new_file('old', root, 'blah', 'id-1', True)
1123
transform, root = self.get_transform()
1125
self.assertEqual([], list(transform.iter_changes()))
1126
old = transform.trans_id_tree_file_id('id-1')
1127
transform.unversion_file(old)
1128
self.assertEqual([('id-1', ('old', None), False, (True, False),
1129
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1130
(True, True))], list(transform.iter_changes()))
1131
transform.new_directory('new', root, 'id-1')
1132
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
1133
('eert_toor', 'eert_toor'), ('old', 'new'),
1134
('file', 'directory'),
1135
(True, False))], list(transform.iter_changes()))
1137
transform.finalize()
1139
def test_iter_changes_new(self):
1140
self.wt.set_root_id('eert_toor')
1141
transform, root = self.get_transform()
1142
transform.new_file('old', root, 'blah')
1144
transform, root = self.get_transform()
1146
old = transform.trans_id_tree_path('old')
1147
transform.version_file('id-1', old)
1148
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
1149
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1150
(False, False))], list(transform.iter_changes()))
1152
transform.finalize()
1154
def test_iter_changes_modifications(self):
1155
self.wt.set_root_id('eert_toor')
1156
transform, root = self.get_transform()
1157
transform.new_file('old', root, 'blah', 'id-1')
1158
transform.new_file('new', root, 'blah')
1159
transform.new_directory('subdir', root, 'subdir-id')
1161
transform, root = self.get_transform()
1163
old = transform.trans_id_tree_path('old')
1164
subdir = transform.trans_id_tree_file_id('subdir-id')
1165
new = transform.trans_id_tree_path('new')
1166
self.assertEqual([], list(transform.iter_changes()))
1169
transform.delete_contents(old)
1170
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1171
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1172
(False, False))], list(transform.iter_changes()))
1175
transform.create_file('blah', old)
1176
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1177
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1178
(False, False))], list(transform.iter_changes()))
1179
transform.cancel_deletion(old)
1180
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1181
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1182
(False, False))], list(transform.iter_changes()))
1183
transform.cancel_creation(old)
1185
# move file_id to a different file
1186
self.assertEqual([], list(transform.iter_changes()))
1187
transform.unversion_file(old)
1188
transform.version_file('id-1', new)
1189
transform.adjust_path('old', root, new)
1190
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1191
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1192
(False, False))], list(transform.iter_changes()))
1193
transform.cancel_versioning(new)
1194
transform._removed_id = set()
1197
self.assertEqual([], list(transform.iter_changes()))
1198
transform.set_executability(True, old)
1199
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1200
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1201
(False, True))], list(transform.iter_changes()))
1202
transform.set_executability(None, old)
1205
self.assertEqual([], list(transform.iter_changes()))
1206
transform.adjust_path('new', root, old)
1207
transform._new_parent = {}
1208
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1209
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1210
(False, False))], list(transform.iter_changes()))
1211
transform._new_name = {}
1214
self.assertEqual([], list(transform.iter_changes()))
1215
transform.adjust_path('new', subdir, old)
1216
transform._new_name = {}
1217
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1218
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1219
('file', 'file'), (False, False))],
1220
list(transform.iter_changes()))
1221
transform._new_path = {}
1224
transform.finalize()
1226
def test_iter_changes_modified_bleed(self):
1227
self.wt.set_root_id('eert_toor')
1228
"""Modified flag should not bleed from one change to another"""
1229
# unfortunately, we have no guarantee that file1 (which is modified)
1230
# will be applied before file2. And if it's applied after file2, it
1231
# obviously can't bleed into file2's change output. But for now, it
1233
transform, root = self.get_transform()
1234
transform.new_file('file1', root, 'blah', 'id-1')
1235
transform.new_file('file2', root, 'blah', 'id-2')
1237
transform, root = self.get_transform()
1239
transform.delete_contents(transform.trans_id_file_id('id-1'))
1240
transform.set_executability(True,
1241
transform.trans_id_file_id('id-2'))
1242
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1243
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1244
('file', None), (False, False)),
1245
('id-2', (u'file2', u'file2'), False, (True, True),
1246
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1247
('file', 'file'), (False, True))],
1248
list(transform.iter_changes()))
1250
transform.finalize()
1252
def test_iter_changes_move_missing(self):
1253
"""Test moving ids with no files around"""
1254
self.wt.set_root_id('toor_eert')
1255
# Need two steps because versioning a non-existant file is a conflict.
1256
transform, root = self.get_transform()
1257
transform.new_directory('floater', root, 'floater-id')
1259
transform, root = self.get_transform()
1260
transform.delete_contents(transform.trans_id_tree_path('floater'))
1262
transform, root = self.get_transform()
1263
floater = transform.trans_id_tree_path('floater')
1265
transform.adjust_path('flitter', root, floater)
1266
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1267
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1268
(None, None), (False, False))], list(transform.iter_changes()))
1270
transform.finalize()
1272
def test_iter_changes_pointless(self):
1273
"""Ensure that no-ops are not treated as modifications"""
1274
self.wt.set_root_id('eert_toor')
1275
transform, root = self.get_transform()
1276
transform.new_file('old', root, 'blah', 'id-1')
1277
transform.new_directory('subdir', root, 'subdir-id')
1279
transform, root = self.get_transform()
1281
old = transform.trans_id_tree_path('old')
1282
subdir = transform.trans_id_tree_file_id('subdir-id')
1283
self.assertEqual([], list(transform.iter_changes()))
1284
transform.delete_contents(subdir)
1285
transform.create_directory(subdir)
1286
transform.set_executability(False, old)
1287
transform.unversion_file(old)
1288
transform.version_file('id-1', old)
1289
transform.adjust_path('old', root, old)
1290
self.assertEqual([], list(transform.iter_changes()))
1292
transform.finalize()
1294
def test_rename_count(self):
1295
transform, root = self.get_transform()
1296
transform.new_file('name1', root, 'contents')
1297
self.assertEqual(transform.rename_count, 0)
1299
self.assertEqual(transform.rename_count, 1)
1300
transform2, root = self.get_transform()
1301
transform2.adjust_path('name2', root,
1302
transform2.trans_id_tree_path('name1'))
1303
self.assertEqual(transform2.rename_count, 0)
1305
self.assertEqual(transform2.rename_count, 2)
1307
def test_change_parent(self):
1308
"""Ensure that after we change a parent, the results are still right.
1310
Renames and parent changes on pending transforms can happen as part
1311
of conflict resolution, and are explicitly permitted by the
1314
This test ensures they work correctly with the rename-avoidance
1317
transform, root = self.get_transform()
1318
parent1 = transform.new_directory('parent1', root)
1319
child1 = transform.new_file('child1', parent1, 'contents')
1320
parent2 = transform.new_directory('parent2', root)
1321
transform.adjust_path('child1', parent2, child1)
1323
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1324
self.assertPathExists(self.wt.abspath('parent2/child1'))
1325
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1326
# no rename for child1 (counting only renames during apply)
1327
self.assertEqual(2, transform.rename_count)
1329
def test_cancel_parent(self):
1330
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1332
This is like the test_change_parent, except that we cancel the parent
1333
before adjusting the path. The transform must detect that the
1334
directory is non-empty, and move children to safe locations.
1336
transform, root = self.get_transform()
1337
parent1 = transform.new_directory('parent1', root)
1338
child1 = transform.new_file('child1', parent1, 'contents')
1339
child2 = transform.new_file('child2', parent1, 'contents')
1341
transform.cancel_creation(parent1)
1343
self.fail('Failed to move child1 before deleting parent1')
1344
transform.cancel_creation(child2)
1345
transform.create_directory(parent1)
1347
transform.cancel_creation(parent1)
1348
# If the transform incorrectly believes that child2 is still in
1349
# parent1's limbo directory, it will try to rename it and fail
1350
# because was already moved by the first cancel_creation.
1352
self.fail('Transform still thinks child2 is a child of parent1')
1353
parent2 = transform.new_directory('parent2', root)
1354
transform.adjust_path('child1', parent2, child1)
1356
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1357
self.assertPathExists(self.wt.abspath('parent2/child1'))
1358
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1359
self.assertEqual(2, transform.rename_count)
1361
def test_adjust_and_cancel(self):
1362
"""Make sure adjust_path keeps track of limbo children properly"""
1363
transform, root = self.get_transform()
1364
parent1 = transform.new_directory('parent1', root)
1365
child1 = transform.new_file('child1', parent1, 'contents')
1366
parent2 = transform.new_directory('parent2', root)
1367
transform.adjust_path('child1', parent2, child1)
1368
transform.cancel_creation(child1)
1370
transform.cancel_creation(parent1)
1371
# if the transform thinks child1 is still in parent1's limbo
1372
# directory, it will attempt to move it and fail.
1374
self.fail('Transform still thinks child1 is a child of parent1')
1375
transform.finalize()
1377
def test_noname_contents(self):
1378
"""TreeTransform should permit deferring naming files."""
1379
transform, root = self.get_transform()
1380
parent = transform.trans_id_file_id('parent-id')
1382
transform.create_directory(parent)
1384
self.fail("Can't handle contents with no name")
1385
transform.finalize()
1387
def test_noname_contents_nested(self):
1388
"""TreeTransform should permit deferring naming files."""
1389
transform, root = self.get_transform()
1390
parent = transform.trans_id_file_id('parent-id')
1392
transform.create_directory(parent)
1394
self.fail("Can't handle contents with no name")
1395
child = transform.new_directory('child', parent)
1396
transform.adjust_path('parent', root, parent)
1398
self.assertPathExists(self.wt.abspath('parent/child'))
1399
self.assertEqual(1, transform.rename_count)
1401
def test_reuse_name(self):
1402
"""Avoid reusing the same limbo name for different files"""
1403
transform, root = self.get_transform()
1404
parent = transform.new_directory('parent', root)
1405
child1 = transform.new_directory('child', parent)
1407
child2 = transform.new_directory('child', parent)
1409
self.fail('Tranform tried to use the same limbo name twice')
1410
transform.adjust_path('child2', parent, child2)
1412
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1413
# child2 is put into top-level limbo because child1 has already
1414
# claimed the direct limbo path when child2 is created. There is no
1415
# advantage in renaming files once they're in top-level limbo, except
1417
self.assertEqual(2, transform.rename_count)
1419
def test_reuse_when_first_moved(self):
1420
"""Don't avoid direct paths when it is safe to use them"""
1421
transform, root = self.get_transform()
1422
parent = transform.new_directory('parent', root)
1423
child1 = transform.new_directory('child', parent)
1424
transform.adjust_path('child1', parent, child1)
1425
child2 = transform.new_directory('child', parent)
1427
# limbo/new-1 => parent
1428
self.assertEqual(1, transform.rename_count)
1430
def test_reuse_after_cancel(self):
1431
"""Don't avoid direct paths when it is safe to use them"""
1432
transform, root = self.get_transform()
1433
parent2 = transform.new_directory('parent2', root)
1434
child1 = transform.new_directory('child1', parent2)
1435
transform.cancel_creation(parent2)
1436
transform.create_directory(parent2)
1437
child2 = transform.new_directory('child1', parent2)
1438
transform.adjust_path('child2', parent2, child1)
1440
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1441
self.assertEqual(2, transform.rename_count)
1443
def test_finalize_order(self):
1444
"""Finalize must be done in child-to-parent order"""
1445
transform, root = self.get_transform()
1446
parent = transform.new_directory('parent', root)
1447
child = transform.new_directory('child', parent)
1449
transform.finalize()
1451
self.fail('Tried to remove parent before child1')
1453
def test_cancel_with_cancelled_child_should_succeed(self):
1454
transform, root = self.get_transform()
1455
parent = transform.new_directory('parent', root)
1456
child = transform.new_directory('child', parent)
1457
transform.cancel_creation(child)
1458
transform.cancel_creation(parent)
1459
transform.finalize()
1461
def test_rollback_on_directory_clash(self):
1463
wt = self.make_branch_and_tree('.')
1464
tt = TreeTransform(wt) # TreeTransform obtains write lock
1466
foo = tt.new_directory('foo', tt.root)
1467
tt.new_file('bar', foo, 'foobar')
1468
baz = tt.new_directory('baz', tt.root)
1469
tt.new_file('qux', baz, 'quux')
1470
# Ask for a rename 'foo' -> 'baz'
1471
tt.adjust_path('baz', tt.root, foo)
1472
# Lie to tt that we've already resolved all conflicts.
1473
tt.apply(no_conflicts=True)
1477
# The rename will fail because the target directory is not empty (but
1478
# raises FileExists anyway).
1479
err = self.assertRaises(errors.FileExists, tt_helper)
1480
self.assertEndsWith(err.path, "/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.assertEndsWith(err.path, "/foo")
1500
def test_two_directories_clash_finalize(self):
1502
wt = self.make_branch_and_tree('.')
1503
tt = TreeTransform(wt) # TreeTransform obtains write lock
1505
foo_1 = tt.new_directory('foo', tt.root)
1506
tt.new_directory('bar', foo_1)
1507
# Adding the same directory with a different content
1508
foo_2 = tt.new_directory('foo', tt.root)
1509
tt.new_directory('baz', foo_2)
1510
# Lie to tt that we've already resolved all conflicts.
1511
tt.apply(no_conflicts=True)
1515
err = self.assertRaises(errors.FileExists, tt_helper)
1516
self.assertEndsWith(err.path, "/foo")
1518
def test_file_to_directory(self):
1519
wt = self.make_branch_and_tree('.')
1520
self.build_tree(['foo'])
1523
tt = TreeTransform(wt)
1524
self.addCleanup(tt.finalize)
1525
foo_trans_id = tt.trans_id_tree_path("foo")
1526
tt.delete_contents(foo_trans_id)
1527
tt.create_directory(foo_trans_id)
1528
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1529
tt.create_file(["aa\n"], bar_trans_id)
1530
tt.version_file("bar-1", bar_trans_id)
1532
self.assertPathExists("foo/bar")
1535
self.assertEqual(wt.kind(wt.path2id("foo")), "directory")
1539
changes = wt.changes_from(wt.basis_tree())
1540
self.assertFalse(changes.has_changed(), changes)
1542
def test_file_to_symlink(self):
1543
self.requireFeature(SymlinkFeature)
1544
wt = self.make_branch_and_tree('.')
1545
self.build_tree(['foo'])
1548
tt = TreeTransform(wt)
1549
self.addCleanup(tt.finalize)
1550
foo_trans_id = tt.trans_id_tree_path("foo")
1551
tt.delete_contents(foo_trans_id)
1552
tt.create_symlink("bar", foo_trans_id)
1554
self.assertPathExists("foo")
1556
self.addCleanup(wt.unlock)
1557
self.assertEqual(wt.kind(wt.path2id("foo")), "symlink")
1559
def test_dir_to_file(self):
1560
wt = self.make_branch_and_tree('.')
1561
self.build_tree(['foo/', 'foo/bar'])
1562
wt.add(['foo', 'foo/bar'])
1564
tt = TreeTransform(wt)
1565
self.addCleanup(tt.finalize)
1566
foo_trans_id = tt.trans_id_tree_path("foo")
1567
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1568
tt.delete_contents(foo_trans_id)
1569
tt.delete_versioned(bar_trans_id)
1570
tt.create_file(["aa\n"], foo_trans_id)
1572
self.assertPathExists("foo")
1574
self.addCleanup(wt.unlock)
1575
self.assertEqual(wt.kind(wt.path2id("foo")), "file")
1577
def test_dir_to_hardlink(self):
1578
self.requireFeature(HardlinkFeature)
1579
wt = self.make_branch_and_tree('.')
1580
self.build_tree(['foo/', 'foo/bar'])
1581
wt.add(['foo', 'foo/bar'])
1583
tt = TreeTransform(wt)
1584
self.addCleanup(tt.finalize)
1585
foo_trans_id = tt.trans_id_tree_path("foo")
1586
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1587
tt.delete_contents(foo_trans_id)
1588
tt.delete_versioned(bar_trans_id)
1589
self.build_tree(['baz'])
1590
tt.create_hardlink("baz", foo_trans_id)
1592
self.assertPathExists("foo")
1593
self.assertPathExists("baz")
1595
self.addCleanup(wt.unlock)
1596
self.assertEqual(wt.kind(wt.path2id("foo")), "file")
1598
def test_no_final_path(self):
1599
transform, root = self.get_transform()
1600
trans_id = transform.trans_id_file_id('foo')
1601
transform.create_file('bar', trans_id)
1602
transform.cancel_creation(trans_id)
1605
def test_create_from_tree(self):
1606
tree1 = self.make_branch_and_tree('tree1')
1607
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1608
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1609
tree2 = self.make_branch_and_tree('tree2')
1610
tt = TreeTransform(tree2)
1611
foo_trans_id = tt.create_path('foo', tt.root)
1612
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1613
bar_trans_id = tt.create_path('bar', tt.root)
1614
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1616
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1617
self.assertFileEqual('baz', 'tree2/bar')
1619
def test_create_from_tree_bytes(self):
1620
"""Provided lines are used instead of tree content."""
1621
tree1 = self.make_branch_and_tree('tree1')
1622
self.build_tree_contents([('tree1/foo', 'bar'),])
1623
tree1.add('foo', 'foo-id')
1624
tree2 = self.make_branch_and_tree('tree2')
1625
tt = TreeTransform(tree2)
1626
foo_trans_id = tt.create_path('foo', tt.root)
1627
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1629
self.assertFileEqual('qux', 'tree2/foo')
1631
def test_create_from_tree_symlink(self):
1632
self.requireFeature(SymlinkFeature)
1633
tree1 = self.make_branch_and_tree('tree1')
1634
os.symlink('bar', 'tree1/foo')
1635
tree1.add('foo', 'foo-id')
1636
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1637
foo_trans_id = tt.create_path('foo', tt.root)
1638
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1640
self.assertEqual('bar', os.readlink('tree2/foo'))
494
1643
class TransformGroup(object):
495
def __init__(self, dirname):
1645
def __init__(self, dirname, root_id):
496
1646
self.name = dirname
497
1647
os.mkdir(dirname)
498
self.wt = BzrDir.create_standalone_workingtree(dirname)
1648
self.wt = ControlDir.create_standalone_workingtree(dirname)
1649
self.wt.set_root_id(root_id)
499
1650
self.b = self.wt.branch
500
1651
self.tt = TreeTransform(self.wt)
501
1652
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
503
1655
def conflict_text(tree, merge):
504
1656
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
505
1657
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1660
class TestInventoryAltered(tests.TestCaseWithTransport):
1662
def test_inventory_altered_unchanged(self):
1663
tree = self.make_branch_and_tree('tree')
1664
self.build_tree(['tree/foo'])
1665
tree.add('foo', 'foo-id')
1666
with TransformPreview(tree) as tt:
1667
self.assertEqual([], tt._inventory_altered())
1669
def test_inventory_altered_changed_parent_id(self):
1670
tree = self.make_branch_and_tree('tree')
1671
self.build_tree(['tree/foo'])
1672
tree.add('foo', 'foo-id')
1673
with TransformPreview(tree) as tt:
1674
tt.unversion_file(tt.root)
1675
tt.version_file('new-id', tt.root)
1676
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1677
foo_tuple = ('foo', foo_trans_id)
1678
root_tuple = ('', tt.root)
1679
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1681
def test_inventory_altered_noop_changed_parent_id(self):
1682
tree = self.make_branch_and_tree('tree')
1683
self.build_tree(['tree/foo'])
1684
tree.add('foo', 'foo-id')
1685
with TransformPreview(tree) as tt:
1686
tt.unversion_file(tt.root)
1687
tt.version_file(tree.get_root_id(), tt.root)
1688
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1689
self.assertEqual([], tt._inventory_altered())
508
1692
class TestTransformMerge(TestCaseInTempDir):
509
1694
def test_text_merge(self):
510
base = TransformGroup("base")
1695
root_id = generate_ids.gen_root_id()
1696
base = TransformGroup("base", root_id)
511
1697
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
512
1698
base.tt.new_file('b', base.root, 'b1', 'b')
513
1699
base.tt.new_file('c', base.root, 'c', 'c')
686
1875
self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
687
1876
self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
689
class TestBuildTree(TestCaseInTempDir):
690
def test_build_tree(self):
691
if not has_symlinks():
692
raise TestSkipped('Test requires symlink support')
1879
class TestBuildTree(tests.TestCaseWithTransport):
1881
def test_build_tree_with_symlinks(self):
1882
self.requireFeature(SymlinkFeature)
694
a = BzrDir.create_standalone_workingtree('a')
1884
a = ControlDir.create_standalone_workingtree('a')
695
1885
os.mkdir('a/foo')
696
file('a/foo/bar', 'wb').write('contents')
1886
with file('a/foo/bar', 'wb') as f: f.write('contents')
697
1887
os.symlink('a/foo/bar', 'a/foo/baz')
698
1888
a.add(['foo', 'foo/bar', 'foo/baz'])
699
1889
a.commit('initial commit')
700
b = BzrDir.create_standalone_workingtree('b')
701
build_tree(a.basis_tree(), b)
1890
b = ControlDir.create_standalone_workingtree('b')
1891
basis = a.basis_tree()
1893
self.addCleanup(basis.unlock)
1894
build_tree(basis, b)
702
1895
self.assertIs(os.path.isdir('b/foo'), True)
703
1896
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
704
1897
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
706
class MockTransform(object):
708
def has_named_child(self, by_parent, parent_id, name):
709
for child_id in by_parent[parent_id]:
713
elif name == "name.~%s~" % child_id:
717
class MockEntry(object):
719
object.__init__(self)
722
class TestGetBackupName(TestCase):
723
def test_get_backup_name(self):
725
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
726
self.assertEqual(name, 'name.~1~')
727
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
728
self.assertEqual(name, 'name.~2~')
729
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
730
self.assertEqual(name, 'name.~1~')
731
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
732
self.assertEqual(name, 'name.~1~')
733
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
734
self.assertEqual(name, 'name.~4~')
1899
def test_build_with_references(self):
1900
tree = self.make_branch_and_tree('source',
1901
format='development-subtree')
1902
subtree = self.make_branch_and_tree('source/subtree',
1903
format='development-subtree')
1904
tree.add_reference(subtree)
1905
tree.commit('a revision')
1906
tree.branch.create_checkout('target')
1907
self.assertPathExists('target')
1908
self.assertPathExists('target/subtree')
1910
def test_file_conflict_handling(self):
1911
"""Ensure that when building trees, conflict handling is done"""
1912
source = self.make_branch_and_tree('source')
1913
target = self.make_branch_and_tree('target')
1914
self.build_tree(['source/file', 'target/file'])
1915
source.add('file', 'new-file')
1916
source.commit('added file')
1917
build_tree(source.basis_tree(), target)
1918
self.assertEqual([DuplicateEntry('Moved existing file to',
1919
'file.moved', 'file', None, 'new-file')],
1921
target2 = self.make_branch_and_tree('target2')
1922
target_file = file('target2/file', 'wb')
1924
source_file = file('source/file', 'rb')
1926
target_file.write(source_file.read())
1931
build_tree(source.basis_tree(), target2)
1932
self.assertEqual([], target2.conflicts())
1934
def test_symlink_conflict_handling(self):
1935
"""Ensure that when building trees, conflict handling is done"""
1936
self.requireFeature(SymlinkFeature)
1937
source = self.make_branch_and_tree('source')
1938
os.symlink('foo', 'source/symlink')
1939
source.add('symlink', 'new-symlink')
1940
source.commit('added file')
1941
target = self.make_branch_and_tree('target')
1942
os.symlink('bar', 'target/symlink')
1943
build_tree(source.basis_tree(), target)
1944
self.assertEqual([DuplicateEntry('Moved existing file to',
1945
'symlink.moved', 'symlink', None, 'new-symlink')],
1947
target = self.make_branch_and_tree('target2')
1948
os.symlink('foo', 'target2/symlink')
1949
build_tree(source.basis_tree(), target)
1950
self.assertEqual([], target.conflicts())
1952
def test_directory_conflict_handling(self):
1953
"""Ensure that when building trees, conflict handling is done"""
1954
source = self.make_branch_and_tree('source')
1955
target = self.make_branch_and_tree('target')
1956
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1957
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1958
source.commit('added file')
1959
build_tree(source.basis_tree(), target)
1960
self.assertEqual([], target.conflicts())
1961
self.assertPathExists('target/dir1/file')
1963
# Ensure contents are merged
1964
target = self.make_branch_and_tree('target2')
1965
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1966
build_tree(source.basis_tree(), target)
1967
self.assertEqual([], target.conflicts())
1968
self.assertPathExists('target2/dir1/file2')
1969
self.assertPathExists('target2/dir1/file')
1971
# Ensure new contents are suppressed for existing branches
1972
target = self.make_branch_and_tree('target3')
1973
self.make_branch('target3/dir1')
1974
self.build_tree(['target3/dir1/file2'])
1975
build_tree(source.basis_tree(), target)
1976
self.assertPathDoesNotExist('target3/dir1/file')
1977
self.assertPathExists('target3/dir1/file2')
1978
self.assertPathExists('target3/dir1.diverted/file')
1979
self.assertEqual([DuplicateEntry('Diverted to',
1980
'dir1.diverted', 'dir1', 'new-dir1', None)],
1983
target = self.make_branch_and_tree('target4')
1984
self.build_tree(['target4/dir1/'])
1985
self.make_branch('target4/dir1/file')
1986
build_tree(source.basis_tree(), target)
1987
self.assertPathExists('target4/dir1/file')
1988
self.assertEqual('directory', file_kind('target4/dir1/file'))
1989
self.assertPathExists('target4/dir1/file.diverted')
1990
self.assertEqual([DuplicateEntry('Diverted to',
1991
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1994
def test_mixed_conflict_handling(self):
1995
"""Ensure that when building trees, conflict handling is done"""
1996
source = self.make_branch_and_tree('source')
1997
target = self.make_branch_and_tree('target')
1998
self.build_tree(['source/name', 'target/name/'])
1999
source.add('name', 'new-name')
2000
source.commit('added file')
2001
build_tree(source.basis_tree(), target)
2002
self.assertEqual([DuplicateEntry('Moved existing file to',
2003
'name.moved', 'name', None, 'new-name')], target.conflicts())
2005
def test_raises_in_populated(self):
2006
source = self.make_branch_and_tree('source')
2007
self.build_tree(['source/name'])
2009
source.commit('added name')
2010
target = self.make_branch_and_tree('target')
2011
self.build_tree(['target/name'])
2013
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
2014
build_tree, source.basis_tree(), target)
2016
def test_build_tree_rename_count(self):
2017
source = self.make_branch_and_tree('source')
2018
self.build_tree(['source/file1', 'source/dir1/'])
2019
source.add(['file1', 'dir1'])
2020
source.commit('add1')
2021
target1 = self.make_branch_and_tree('target1')
2022
transform_result = build_tree(source.basis_tree(), target1)
2023
self.assertEqual(2, transform_result.rename_count)
2025
self.build_tree(['source/dir1/file2'])
2026
source.add(['dir1/file2'])
2027
source.commit('add3')
2028
target2 = self.make_branch_and_tree('target2')
2029
transform_result = build_tree(source.basis_tree(), target2)
2030
# children of non-root directories should not be renamed
2031
self.assertEqual(2, transform_result.rename_count)
2033
def create_ab_tree(self):
2034
"""Create a committed test tree with two files"""
2035
source = self.make_branch_and_tree('source')
2036
self.build_tree_contents([('source/file1', 'A')])
2037
self.build_tree_contents([('source/file2', 'B')])
2038
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2039
source.commit('commit files')
2041
self.addCleanup(source.unlock)
2044
def test_build_tree_accelerator_tree(self):
2045
source = self.create_ab_tree()
2046
self.build_tree_contents([('source/file2', 'C')])
2048
real_source_get_file = source.get_file
2049
def get_file(file_id, path=None):
2050
calls.append(file_id)
2051
return real_source_get_file(file_id, path)
2052
source.get_file = get_file
2053
target = self.make_branch_and_tree('target')
2054
revision_tree = source.basis_tree()
2055
revision_tree.lock_read()
2056
self.addCleanup(revision_tree.unlock)
2057
build_tree(revision_tree, target, source)
2058
self.assertEqual(['file1-id'], calls)
2060
self.addCleanup(target.unlock)
2061
self.assertEqual([], list(target.iter_changes(revision_tree)))
2063
def test_build_tree_accelerator_tree_observes_sha1(self):
2064
source = self.create_ab_tree()
2065
sha1 = osutils.sha_string('A')
2066
target = self.make_branch_and_tree('target')
2068
self.addCleanup(target.unlock)
2069
state = target.current_dirstate()
2070
state._cutoff_time = time.time() + 60
2071
build_tree(source.basis_tree(), target, source)
2072
entry = state._get_entry(0, path_utf8='file1')
2073
self.assertEqual(sha1, entry[1][0][1])
2075
def test_build_tree_accelerator_tree_missing_file(self):
2076
source = self.create_ab_tree()
2077
os.unlink('source/file1')
2078
source.remove(['file2'])
2079
target = self.make_branch_and_tree('target')
2080
revision_tree = source.basis_tree()
2081
revision_tree.lock_read()
2082
self.addCleanup(revision_tree.unlock)
2083
build_tree(revision_tree, target, source)
2085
self.addCleanup(target.unlock)
2086
self.assertEqual([], list(target.iter_changes(revision_tree)))
2088
def test_build_tree_accelerator_wrong_kind(self):
2089
self.requireFeature(SymlinkFeature)
2090
source = self.make_branch_and_tree('source')
2091
self.build_tree_contents([('source/file1', '')])
2092
self.build_tree_contents([('source/file2', '')])
2093
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2094
source.commit('commit files')
2095
os.unlink('source/file2')
2096
self.build_tree_contents([('source/file2/', 'C')])
2097
os.unlink('source/file1')
2098
os.symlink('file2', 'source/file1')
2100
real_source_get_file = source.get_file
2101
def get_file(file_id, path=None):
2102
calls.append(file_id)
2103
return real_source_get_file(file_id, path)
2104
source.get_file = get_file
2105
target = self.make_branch_and_tree('target')
2106
revision_tree = source.basis_tree()
2107
revision_tree.lock_read()
2108
self.addCleanup(revision_tree.unlock)
2109
build_tree(revision_tree, target, source)
2110
self.assertEqual([], calls)
2112
self.addCleanup(target.unlock)
2113
self.assertEqual([], list(target.iter_changes(revision_tree)))
2115
def test_build_tree_hardlink(self):
2116
self.requireFeature(HardlinkFeature)
2117
source = self.create_ab_tree()
2118
target = self.make_branch_and_tree('target')
2119
revision_tree = source.basis_tree()
2120
revision_tree.lock_read()
2121
self.addCleanup(revision_tree.unlock)
2122
build_tree(revision_tree, target, source, hardlink=True)
2124
self.addCleanup(target.unlock)
2125
self.assertEqual([], list(target.iter_changes(revision_tree)))
2126
source_stat = os.stat('source/file1')
2127
target_stat = os.stat('target/file1')
2128
self.assertEqual(source_stat, target_stat)
2130
# Explicitly disallowing hardlinks should prevent them.
2131
target2 = self.make_branch_and_tree('target2')
2132
build_tree(revision_tree, target2, source, hardlink=False)
2134
self.addCleanup(target2.unlock)
2135
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2136
source_stat = os.stat('source/file1')
2137
target2_stat = os.stat('target2/file1')
2138
self.assertNotEqual(source_stat, target2_stat)
2140
def test_build_tree_accelerator_tree_moved(self):
2141
source = self.make_branch_and_tree('source')
2142
self.build_tree_contents([('source/file1', 'A')])
2143
source.add(['file1'], ['file1-id'])
2144
source.commit('commit files')
2145
source.rename_one('file1', 'file2')
2147
self.addCleanup(source.unlock)
2148
target = self.make_branch_and_tree('target')
2149
revision_tree = source.basis_tree()
2150
revision_tree.lock_read()
2151
self.addCleanup(revision_tree.unlock)
2152
build_tree(revision_tree, target, source)
2154
self.addCleanup(target.unlock)
2155
self.assertEqual([], list(target.iter_changes(revision_tree)))
2157
def test_build_tree_hardlinks_preserve_execute(self):
2158
self.requireFeature(HardlinkFeature)
2159
source = self.create_ab_tree()
2160
tt = TreeTransform(source)
2161
trans_id = tt.trans_id_tree_file_id('file1-id')
2162
tt.set_executability(True, trans_id)
2164
self.assertTrue(source.is_executable('file1-id'))
2165
target = self.make_branch_and_tree('target')
2166
revision_tree = source.basis_tree()
2167
revision_tree.lock_read()
2168
self.addCleanup(revision_tree.unlock)
2169
build_tree(revision_tree, target, source, hardlink=True)
2171
self.addCleanup(target.unlock)
2172
self.assertEqual([], list(target.iter_changes(revision_tree)))
2173
self.assertTrue(source.is_executable('file1-id'))
2175
def install_rot13_content_filter(self, pattern):
2177
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2178
# below, but that looks a bit... hard to read even if it's exactly
2180
original_registry = filters._reset_registry()
2181
def restore_registry():
2182
filters._reset_registry(original_registry)
2183
self.addCleanup(restore_registry)
2184
def rot13(chunks, context=None):
2185
return [''.join(chunks).encode('rot13')]
2186
rot13filter = filters.ContentFilter(rot13, rot13)
2187
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
2188
os.mkdir(self.test_home_dir + '/.bazaar')
2189
rules_filename = self.test_home_dir + '/.bazaar/rules'
2190
f = open(rules_filename, 'wb')
2191
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2193
def uninstall_rules():
2194
os.remove(rules_filename)
2196
self.addCleanup(uninstall_rules)
2199
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2200
"""build_tree will not hardlink files that have content filtering rules
2201
applied to them (but will still hardlink other files from the same tree
2204
self.requireFeature(HardlinkFeature)
2205
self.install_rot13_content_filter('file1')
2206
source = self.create_ab_tree()
2207
target = self.make_branch_and_tree('target')
2208
revision_tree = source.basis_tree()
2209
revision_tree.lock_read()
2210
self.addCleanup(revision_tree.unlock)
2211
build_tree(revision_tree, target, source, hardlink=True)
2213
self.addCleanup(target.unlock)
2214
self.assertEqual([], list(target.iter_changes(revision_tree)))
2215
source_stat = os.stat('source/file1')
2216
target_stat = os.stat('target/file1')
2217
self.assertNotEqual(source_stat, target_stat)
2218
source_stat = os.stat('source/file2')
2219
target_stat = os.stat('target/file2')
2220
self.assertEqualStat(source_stat, target_stat)
2222
def test_case_insensitive_build_tree_inventory(self):
2223
if (features.CaseInsensitiveFilesystemFeature.available()
2224
or features.CaseInsCasePresFilenameFeature.available()):
2225
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2226
source = self.make_branch_and_tree('source')
2227
self.build_tree(['source/file', 'source/FILE'])
2228
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2229
source.commit('added files')
2230
# Don't try this at home, kids!
2231
# Force the tree to report that it is case insensitive
2232
target = self.make_branch_and_tree('target')
2233
target.case_sensitive = False
2234
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2235
self.assertEqual('file.moved', target.id2path('lower-id'))
2236
self.assertEqual('FILE', target.id2path('upper-id'))
2238
def test_build_tree_observes_sha(self):
2239
source = self.make_branch_and_tree('source')
2240
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2241
source.add(['file1', 'dir', 'dir/file2'],
2242
['file1-id', 'dir-id', 'file2-id'])
2243
source.commit('new files')
2244
target = self.make_branch_and_tree('target')
2246
self.addCleanup(target.unlock)
2247
# We make use of the fact that DirState caches its cutoff time. So we
2248
# set the 'safe' time to one minute in the future.
2249
state = target.current_dirstate()
2250
state._cutoff_time = time.time() + 60
2251
build_tree(source.basis_tree(), target)
2252
entry1_sha = osutils.sha_file_by_name('source/file1')
2253
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2254
# entry[1] is the state information, entry[1][0] is the state of the
2255
# working tree, entry[1][0][1] is the sha value for the current working
2257
entry1 = state._get_entry(0, path_utf8='file1')
2258
self.assertEqual(entry1_sha, entry1[1][0][1])
2259
# The 'size' field must also be set.
2260
self.assertEqual(25, entry1[1][0][2])
2261
entry1_state = entry1[1][0]
2262
entry2 = state._get_entry(0, path_utf8='dir/file2')
2263
self.assertEqual(entry2_sha, entry2[1][0][1])
2264
self.assertEqual(29, entry2[1][0][2])
2265
entry2_state = entry2[1][0]
2266
# Now, make sure that we don't have to re-read the content. The
2267
# packed_stat should match exactly.
2268
self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
2269
self.assertEqual(entry2_sha,
2270
target.get_file_sha1('file2-id', 'dir/file2'))
2271
self.assertEqual(entry1_state, entry1[1][0])
2272
self.assertEqual(entry2_state, entry2[1][0])
2275
class TestCommitTransform(tests.TestCaseWithTransport):
2277
def get_branch(self):
2278
tree = self.make_branch_and_tree('tree')
2280
self.addCleanup(tree.unlock)
2281
tree.commit('empty commit')
2284
def get_branch_and_transform(self):
2285
branch = self.get_branch()
2286
tt = TransformPreview(branch.basis_tree())
2287
self.addCleanup(tt.finalize)
2290
def test_commit_wrong_basis(self):
2291
branch = self.get_branch()
2292
basis = branch.repository.revision_tree(
2293
_mod_revision.NULL_REVISION)
2294
tt = TransformPreview(basis)
2295
self.addCleanup(tt.finalize)
2296
e = self.assertRaises(ValueError, tt.commit, branch, '')
2297
self.assertEqual('TreeTransform not based on branch basis: null:',
2300
def test_empy_commit(self):
2301
branch, tt = self.get_branch_and_transform()
2302
rev = tt.commit(branch, 'my message')
2303
self.assertEqual(2, branch.revno())
2304
repo = branch.repository
2305
self.assertEqual('my message', repo.get_revision(rev).message)
2307
def test_merge_parents(self):
2308
branch, tt = self.get_branch_and_transform()
2309
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2310
self.assertEqual(['rev1b', 'rev1c'],
2311
branch.basis_tree().get_parent_ids()[1:])
2313
def test_first_commit(self):
2314
branch = self.make_branch('branch')
2316
self.addCleanup(branch.unlock)
2317
tt = TransformPreview(branch.basis_tree())
2318
self.addCleanup(tt.finalize)
2319
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2320
rev = tt.commit(branch, 'my message')
2321
self.assertEqual([], branch.basis_tree().get_parent_ids())
2322
self.assertNotEqual(_mod_revision.NULL_REVISION,
2323
branch.last_revision())
2325
def test_first_commit_with_merge_parents(self):
2326
branch = self.make_branch('branch')
2328
self.addCleanup(branch.unlock)
2329
tt = TransformPreview(branch.basis_tree())
2330
self.addCleanup(tt.finalize)
2331
e = self.assertRaises(ValueError, tt.commit, branch,
2332
'my message', ['rev1b-id'])
2333
self.assertEqual('Cannot supply merge parents for first commit.',
2335
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2337
def test_add_files(self):
2338
branch, tt = self.get_branch_and_transform()
2339
tt.new_file('file', tt.root, 'contents', 'file-id')
2340
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2341
if SymlinkFeature.available():
2342
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2343
rev = tt.commit(branch, 'message')
2344
tree = branch.basis_tree()
2345
self.assertEqual('file', tree.id2path('file-id'))
2346
self.assertEqual('contents', tree.get_file_text('file-id'))
2347
self.assertEqual('dir', tree.id2path('dir-id'))
2348
if SymlinkFeature.available():
2349
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2350
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2352
def test_add_unversioned(self):
2353
branch, tt = self.get_branch_and_transform()
2354
tt.new_file('file', tt.root, 'contents')
2355
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2356
'message', strict=True)
2358
def test_modify_strict(self):
2359
branch, tt = self.get_branch_and_transform()
2360
tt.new_file('file', tt.root, 'contents', 'file-id')
2361
tt.commit(branch, 'message', strict=True)
2362
tt = TransformPreview(branch.basis_tree())
2363
self.addCleanup(tt.finalize)
2364
trans_id = tt.trans_id_file_id('file-id')
2365
tt.delete_contents(trans_id)
2366
tt.create_file('contents', trans_id)
2367
tt.commit(branch, 'message', strict=True)
2369
def test_commit_malformed(self):
2370
"""Committing a malformed transform should raise an exception.
2372
In this case, we are adding a file without adding its parent.
2374
branch, tt = self.get_branch_and_transform()
2375
parent_id = tt.trans_id_file_id('parent-id')
2376
tt.new_file('file', parent_id, 'contents', 'file-id')
2377
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2380
def test_commit_rich_revision_data(self):
2381
branch, tt = self.get_branch_and_transform()
2382
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2383
committer='me <me@example.com>',
2384
revprops={'foo': 'bar'}, revision_id='revid-1',
2385
authors=['Author1 <author1@example.com>',
2386
'Author2 <author2@example.com>',
2388
self.assertEqual('revid-1', rev_id)
2389
revision = branch.repository.get_revision(rev_id)
2390
self.assertEqual(1, revision.timestamp)
2391
self.assertEqual(43201, revision.timezone)
2392
self.assertEqual('me <me@example.com>', revision.committer)
2393
self.assertEqual(['Author1 <author1@example.com>',
2394
'Author2 <author2@example.com>'],
2395
revision.get_apparent_authors())
2396
del revision.properties['authors']
2397
self.assertEqual({'foo': 'bar',
2398
'branch-nick': 'tree'},
2399
revision.properties)
2401
def test_no_explicit_revprops(self):
2402
branch, tt = self.get_branch_and_transform()
2403
rev_id = tt.commit(branch, 'message', authors=[
2404
'Author1 <author1@example.com>',
2405
'Author2 <author2@example.com>', ])
2406
revision = branch.repository.get_revision(rev_id)
2407
self.assertEqual(['Author1 <author1@example.com>',
2408
'Author2 <author2@example.com>'],
2409
revision.get_apparent_authors())
2410
self.assertEqual('tree', revision.properties['branch-nick'])
2413
class TestFileMover(tests.TestCaseWithTransport):
2415
def test_file_mover(self):
2416
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2417
mover = _FileMover()
2418
mover.rename('a', 'q')
2419
self.assertPathExists('q')
2420
self.assertPathDoesNotExist('a')
2421
self.assertPathExists('q/b')
2422
self.assertPathExists('c')
2423
self.assertPathExists('c/d')
2425
def test_pre_delete_rollback(self):
2426
self.build_tree(['a/'])
2427
mover = _FileMover()
2428
mover.pre_delete('a', 'q')
2429
self.assertPathExists('q')
2430
self.assertPathDoesNotExist('a')
2432
self.assertPathDoesNotExist('q')
2433
self.assertPathExists('a')
2435
def test_apply_deletions(self):
2436
self.build_tree(['a/', 'b/'])
2437
mover = _FileMover()
2438
mover.pre_delete('a', 'q')
2439
mover.pre_delete('b', 'r')
2440
self.assertPathExists('q')
2441
self.assertPathExists('r')
2442
self.assertPathDoesNotExist('a')
2443
self.assertPathDoesNotExist('b')
2444
mover.apply_deletions()
2445
self.assertPathDoesNotExist('q')
2446
self.assertPathDoesNotExist('r')
2447
self.assertPathDoesNotExist('a')
2448
self.assertPathDoesNotExist('b')
2450
def test_file_mover_rollback(self):
2451
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2452
mover = _FileMover()
2453
mover.rename('c/d', 'c/f')
2454
mover.rename('c/e', 'c/d')
2456
mover.rename('a', 'c')
2457
except errors.FileExists, e:
2459
self.assertPathExists('a')
2460
self.assertPathExists('c/d')
2463
class Bogus(Exception):
2467
class TestTransformRollback(tests.TestCaseWithTransport):
2469
class ExceptionFileMover(_FileMover):
2471
def __init__(self, bad_source=None, bad_target=None):
2472
_FileMover.__init__(self)
2473
self.bad_source = bad_source
2474
self.bad_target = bad_target
2476
def rename(self, source, target):
2477
if (self.bad_source is not None and
2478
source.endswith(self.bad_source)):
2480
elif (self.bad_target is not None and
2481
target.endswith(self.bad_target)):
2484
_FileMover.rename(self, source, target)
2486
def test_rollback_rename(self):
2487
tree = self.make_branch_and_tree('.')
2488
self.build_tree(['a/', 'a/b'])
2489
tt = TreeTransform(tree)
2490
self.addCleanup(tt.finalize)
2491
a_id = tt.trans_id_tree_path('a')
2492
tt.adjust_path('c', tt.root, a_id)
2493
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2494
self.assertRaises(Bogus, tt.apply,
2495
_mover=self.ExceptionFileMover(bad_source='a'))
2496
self.assertPathExists('a')
2497
self.assertPathExists('a/b')
2499
self.assertPathExists('c')
2500
self.assertPathExists('c/d')
2502
def test_rollback_rename_into_place(self):
2503
tree = self.make_branch_and_tree('.')
2504
self.build_tree(['a/', 'a/b'])
2505
tt = TreeTransform(tree)
2506
self.addCleanup(tt.finalize)
2507
a_id = tt.trans_id_tree_path('a')
2508
tt.adjust_path('c', tt.root, a_id)
2509
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2510
self.assertRaises(Bogus, tt.apply,
2511
_mover=self.ExceptionFileMover(bad_target='c/d'))
2512
self.assertPathExists('a')
2513
self.assertPathExists('a/b')
2515
self.assertPathExists('c')
2516
self.assertPathExists('c/d')
2518
def test_rollback_deletion(self):
2519
tree = self.make_branch_and_tree('.')
2520
self.build_tree(['a/', 'a/b'])
2521
tt = TreeTransform(tree)
2522
self.addCleanup(tt.finalize)
2523
a_id = tt.trans_id_tree_path('a')
2524
tt.delete_contents(a_id)
2525
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2526
self.assertRaises(Bogus, tt.apply,
2527
_mover=self.ExceptionFileMover(bad_target='d'))
2528
self.assertPathExists('a')
2529
self.assertPathExists('a/b')
2532
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2533
"""Ensure treetransform creation errors can be safely cleaned up after"""
2535
def _override_globals_in_method(self, instance, method_name, globals):
2536
"""Replace method on instance with one with updated globals"""
2538
func = getattr(instance, method_name).im_func
2539
new_globals = dict(func.func_globals)
2540
new_globals.update(globals)
2541
new_func = types.FunctionType(func.func_code, new_globals,
2542
func.func_name, func.func_defaults)
2543
setattr(instance, method_name,
2544
types.MethodType(new_func, instance, instance.__class__))
2545
self.addCleanup(delattr, instance, method_name)
2548
def _fake_open_raises_before(name, mode):
2549
"""Like open() but raises before doing anything"""
2553
def _fake_open_raises_after(name, mode):
2554
"""Like open() but raises after creating file without returning"""
2555
open(name, mode).close()
2558
def create_transform_and_root_trans_id(self):
2559
"""Setup a transform creating a file in limbo"""
2560
tree = self.make_branch_and_tree('.')
2561
tt = TreeTransform(tree)
2562
return tt, tt.create_path("a", tt.root)
2564
def create_transform_and_subdir_trans_id(self):
2565
"""Setup a transform creating a directory containing a file in limbo"""
2566
tree = self.make_branch_and_tree('.')
2567
tt = TreeTransform(tree)
2568
d_trans_id = tt.create_path("d", tt.root)
2569
tt.create_directory(d_trans_id)
2570
f_trans_id = tt.create_path("a", d_trans_id)
2571
tt.adjust_path("a", d_trans_id, f_trans_id)
2572
return tt, f_trans_id
2574
def test_root_create_file_open_raises_before_creation(self):
2575
tt, trans_id = self.create_transform_and_root_trans_id()
2576
self._override_globals_in_method(tt, "create_file",
2577
{"open": self._fake_open_raises_before})
2578
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2579
path = tt._limbo_name(trans_id)
2580
self.assertPathDoesNotExist(path)
2582
self.assertPathDoesNotExist(tt._limbodir)
2584
def test_root_create_file_open_raises_after_creation(self):
2585
tt, trans_id = self.create_transform_and_root_trans_id()
2586
self._override_globals_in_method(tt, "create_file",
2587
{"open": self._fake_open_raises_after})
2588
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2589
path = tt._limbo_name(trans_id)
2590
self.assertPathExists(path)
2592
self.assertPathDoesNotExist(path)
2593
self.assertPathDoesNotExist(tt._limbodir)
2595
def test_subdir_create_file_open_raises_before_creation(self):
2596
tt, trans_id = self.create_transform_and_subdir_trans_id()
2597
self._override_globals_in_method(tt, "create_file",
2598
{"open": self._fake_open_raises_before})
2599
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2600
path = tt._limbo_name(trans_id)
2601
self.assertPathDoesNotExist(path)
2603
self.assertPathDoesNotExist(tt._limbodir)
2605
def test_subdir_create_file_open_raises_after_creation(self):
2606
tt, trans_id = self.create_transform_and_subdir_trans_id()
2607
self._override_globals_in_method(tt, "create_file",
2608
{"open": self._fake_open_raises_after})
2609
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2610
path = tt._limbo_name(trans_id)
2611
self.assertPathExists(path)
2613
self.assertPathDoesNotExist(path)
2614
self.assertPathDoesNotExist(tt._limbodir)
2616
def test_rename_in_limbo_rename_raises_after_rename(self):
2617
tt, trans_id = self.create_transform_and_root_trans_id()
2618
parent1 = tt.new_directory('parent1', tt.root)
2619
child1 = tt.new_file('child1', parent1, 'contents')
2620
parent2 = tt.new_directory('parent2', tt.root)
2622
class FakeOSModule(object):
2623
def rename(self, old, new):
2626
self._override_globals_in_method(tt, "_rename_in_limbo",
2627
{"os": FakeOSModule()})
2629
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2630
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2631
self.assertPathExists(path)
2633
self.assertPathDoesNotExist(path)
2634
self.assertPathDoesNotExist(tt._limbodir)
2636
def test_rename_in_limbo_rename_raises_before_rename(self):
2637
tt, trans_id = self.create_transform_and_root_trans_id()
2638
parent1 = tt.new_directory('parent1', tt.root)
2639
child1 = tt.new_file('child1', parent1, 'contents')
2640
parent2 = tt.new_directory('parent2', tt.root)
2642
class FakeOSModule(object):
2643
def rename(self, old, new):
2645
self._override_globals_in_method(tt, "_rename_in_limbo",
2646
{"os": FakeOSModule()})
2648
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2649
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2650
self.assertPathExists(path)
2652
self.assertPathDoesNotExist(path)
2653
self.assertPathDoesNotExist(tt._limbodir)
2656
class TestTransformMissingParent(tests.TestCaseWithTransport):
2658
def make_tt_with_versioned_dir(self):
2659
wt = self.make_branch_and_tree('.')
2660
self.build_tree(['dir/',])
2661
wt.add(['dir'], ['dir-id'])
2662
wt.commit('Create dir')
2663
tt = TreeTransform(wt)
2664
self.addCleanup(tt.finalize)
2667
def test_resolve_create_parent_for_versioned_file(self):
2668
wt, tt = self.make_tt_with_versioned_dir()
2669
dir_tid = tt.trans_id_tree_file_id('dir-id')
2670
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2671
tt.delete_contents(dir_tid)
2672
tt.unversion_file(dir_tid)
2673
conflicts = resolve_conflicts(tt)
2674
# one conflict for the missing directory, one for the unversioned
2676
self.assertLength(2, conflicts)
2678
def test_non_versioned_file_create_conflict(self):
2679
wt, tt = self.make_tt_with_versioned_dir()
2680
dir_tid = tt.trans_id_tree_file_id('dir-id')
2681
tt.new_file('file', dir_tid, 'Contents')
2682
tt.delete_contents(dir_tid)
2683
tt.unversion_file(dir_tid)
2684
conflicts = resolve_conflicts(tt)
2685
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2686
self.assertLength(1, conflicts)
2687
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2691
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2692
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2694
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2695
('', ''), ('directory', 'directory'), (False, False))
2698
class TestTransformPreview(tests.TestCaseWithTransport):
2700
def create_tree(self):
2701
tree = self.make_branch_and_tree('.')
2702
self.build_tree_contents([('a', 'content 1')])
2703
tree.set_root_id('TREE_ROOT')
2704
tree.add('a', 'a-id')
2705
tree.commit('rev1', rev_id='rev1')
2706
return tree.branch.repository.revision_tree('rev1')
2708
def get_empty_preview(self):
2709
repository = self.make_repository('repo')
2710
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2711
preview = TransformPreview(tree)
2712
self.addCleanup(preview.finalize)
2715
def test_transform_preview(self):
2716
revision_tree = self.create_tree()
2717
preview = TransformPreview(revision_tree)
2718
self.addCleanup(preview.finalize)
2720
def test_transform_preview_tree(self):
2721
revision_tree = self.create_tree()
2722
preview = TransformPreview(revision_tree)
2723
self.addCleanup(preview.finalize)
2724
preview.get_preview_tree()
2726
def test_transform_new_file(self):
2727
revision_tree = self.create_tree()
2728
preview = TransformPreview(revision_tree)
2729
self.addCleanup(preview.finalize)
2730
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2731
preview_tree = preview.get_preview_tree()
2732
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2734
preview_tree.get_file('file2-id').read(), 'content B\n')
2736
def test_diff_preview_tree(self):
2737
revision_tree = self.create_tree()
2738
preview = TransformPreview(revision_tree)
2739
self.addCleanup(preview.finalize)
2740
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2741
preview_tree = preview.get_preview_tree()
2743
show_diff_trees(revision_tree, preview_tree, out)
2744
lines = out.getvalue().splitlines()
2745
self.assertEqual(lines[0], "=== added file 'file2'")
2746
# 3 lines of diff administrivia
2747
self.assertEqual(lines[4], "+content B")
2749
def test_transform_conflicts(self):
2750
revision_tree = self.create_tree()
2751
preview = TransformPreview(revision_tree)
2752
self.addCleanup(preview.finalize)
2753
preview.new_file('a', preview.root, 'content 2')
2754
resolve_conflicts(preview)
2755
trans_id = preview.trans_id_file_id('a-id')
2756
self.assertEqual('a.moved', preview.final_name(trans_id))
2758
def get_tree_and_preview_tree(self):
2759
revision_tree = self.create_tree()
2760
preview = TransformPreview(revision_tree)
2761
self.addCleanup(preview.finalize)
2762
a_trans_id = preview.trans_id_file_id('a-id')
2763
preview.delete_contents(a_trans_id)
2764
preview.create_file('b content', a_trans_id)
2765
preview_tree = preview.get_preview_tree()
2766
return revision_tree, preview_tree
2768
def test_iter_changes(self):
2769
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2770
root = revision_tree.get_root_id()
2771
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2772
(root, root), ('a', 'a'), ('file', 'file'),
2774
list(preview_tree.iter_changes(revision_tree)))
2776
def test_include_unchanged_succeeds(self):
2777
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2778
changes = preview_tree.iter_changes(revision_tree,
2779
include_unchanged=True)
2780
root = revision_tree.get_root_id()
2782
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2784
def test_specific_files(self):
2785
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2786
changes = preview_tree.iter_changes(revision_tree,
2787
specific_files=[''])
2788
self.assertEqual([A_ENTRY], list(changes))
2790
def test_want_unversioned(self):
2791
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2792
changes = preview_tree.iter_changes(revision_tree,
2793
want_unversioned=True)
2794
self.assertEqual([A_ENTRY], list(changes))
2796
def test_ignore_extra_trees_no_specific_files(self):
2797
# extra_trees is harmless without specific_files, so we'll silently
2798
# accept it, even though we won't use it.
2799
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2800
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2802
def test_ignore_require_versioned_no_specific_files(self):
2803
# require_versioned is meaningless without specific_files.
2804
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2805
preview_tree.iter_changes(revision_tree, require_versioned=False)
2807
def test_ignore_pb(self):
2808
# pb could be supported, but TT.iter_changes doesn't support it.
2809
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2810
preview_tree.iter_changes(revision_tree)
2812
def test_kind(self):
2813
revision_tree = self.create_tree()
2814
preview = TransformPreview(revision_tree)
2815
self.addCleanup(preview.finalize)
2816
preview.new_file('file', preview.root, 'contents', 'file-id')
2817
preview.new_directory('directory', preview.root, 'dir-id')
2818
preview_tree = preview.get_preview_tree()
2819
self.assertEqual('file', preview_tree.kind('file-id'))
2820
self.assertEqual('directory', preview_tree.kind('dir-id'))
2822
def test_get_file_mtime(self):
2823
preview = self.get_empty_preview()
2824
file_trans_id = preview.new_file('file', preview.root, 'contents',
2826
limbo_path = preview._limbo_name(file_trans_id)
2827
preview_tree = preview.get_preview_tree()
2828
self.assertEqual(os.stat(limbo_path).st_mtime,
2829
preview_tree.get_file_mtime('file-id'))
2831
def test_get_file_mtime_renamed(self):
2832
work_tree = self.make_branch_and_tree('tree')
2833
self.build_tree(['tree/file'])
2834
work_tree.add('file', 'file-id')
2835
preview = TransformPreview(work_tree)
2836
self.addCleanup(preview.finalize)
2837
file_trans_id = preview.trans_id_tree_file_id('file-id')
2838
preview.adjust_path('renamed', preview.root, file_trans_id)
2839
preview_tree = preview.get_preview_tree()
2840
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2841
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2843
def test_get_file_size(self):
2844
work_tree = self.make_branch_and_tree('tree')
2845
self.build_tree_contents([('tree/old', 'old')])
2846
work_tree.add('old', 'old-id')
2847
preview = TransformPreview(work_tree)
2848
self.addCleanup(preview.finalize)
2849
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2851
tree = preview.get_preview_tree()
2852
self.assertEqual(len('old'), tree.get_file_size('old-id'))
2853
self.assertEqual(len('contents'), tree.get_file_size('new-id'))
2855
def test_get_file(self):
2856
preview = self.get_empty_preview()
2857
preview.new_file('file', preview.root, 'contents', 'file-id')
2858
preview_tree = preview.get_preview_tree()
2859
tree_file = preview_tree.get_file('file-id')
2861
self.assertEqual('contents', tree_file.read())
2865
def test_get_symlink_target(self):
2866
self.requireFeature(SymlinkFeature)
2867
preview = self.get_empty_preview()
2868
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2869
preview_tree = preview.get_preview_tree()
2870
self.assertEqual('target',
2871
preview_tree.get_symlink_target('symlink-id'))
2873
def test_all_file_ids(self):
2874
tree = self.make_branch_and_tree('tree')
2875
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2876
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2877
preview = TransformPreview(tree)
2878
self.addCleanup(preview.finalize)
2879
preview.unversion_file(preview.trans_id_file_id('b-id'))
2880
c_trans_id = preview.trans_id_file_id('c-id')
2881
preview.unversion_file(c_trans_id)
2882
preview.version_file('c-id', c_trans_id)
2883
preview_tree = preview.get_preview_tree()
2884
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2885
preview_tree.all_file_ids())
2887
def test_path2id_deleted_unchanged(self):
2888
tree = self.make_branch_and_tree('tree')
2889
self.build_tree(['tree/unchanged', 'tree/deleted'])
2890
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2891
preview = TransformPreview(tree)
2892
self.addCleanup(preview.finalize)
2893
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2894
preview_tree = preview.get_preview_tree()
2895
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2896
self.assertIs(None, preview_tree.path2id('deleted'))
2898
def test_path2id_created(self):
2899
tree = self.make_branch_and_tree('tree')
2900
self.build_tree(['tree/unchanged'])
2901
tree.add(['unchanged'], ['unchanged-id'])
2902
preview = TransformPreview(tree)
2903
self.addCleanup(preview.finalize)
2904
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2905
'contents', 'new-id')
2906
preview_tree = preview.get_preview_tree()
2907
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2909
def test_path2id_moved(self):
2910
tree = self.make_branch_and_tree('tree')
2911
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2912
tree.add(['old_parent', 'old_parent/child'],
2913
['old_parent-id', 'child-id'])
2914
preview = TransformPreview(tree)
2915
self.addCleanup(preview.finalize)
2916
new_parent = preview.new_directory('new_parent', preview.root,
2918
preview.adjust_path('child', new_parent,
2919
preview.trans_id_file_id('child-id'))
2920
preview_tree = preview.get_preview_tree()
2921
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2922
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2924
def test_path2id_renamed_parent(self):
2925
tree = self.make_branch_and_tree('tree')
2926
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2927
tree.add(['old_name', 'old_name/child'],
2928
['parent-id', 'child-id'])
2929
preview = TransformPreview(tree)
2930
self.addCleanup(preview.finalize)
2931
preview.adjust_path('new_name', preview.root,
2932
preview.trans_id_file_id('parent-id'))
2933
preview_tree = preview.get_preview_tree()
2934
self.assertIs(None, preview_tree.path2id('old_name/child'))
2935
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2937
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2938
preview_tree = tt.get_preview_tree()
2939
preview_result = list(preview_tree.iter_entries_by_dir(
2943
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2944
self.assertEqual(actual_result, preview_result)
2946
def test_iter_entries_by_dir_new(self):
2947
tree = self.make_branch_and_tree('tree')
2948
tt = TreeTransform(tree)
2949
tt.new_file('new', tt.root, 'contents', 'new-id')
2950
self.assertMatchingIterEntries(tt)
2952
def test_iter_entries_by_dir_deleted(self):
2953
tree = self.make_branch_and_tree('tree')
2954
self.build_tree(['tree/deleted'])
2955
tree.add('deleted', 'deleted-id')
2956
tt = TreeTransform(tree)
2957
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2958
self.assertMatchingIterEntries(tt)
2960
def test_iter_entries_by_dir_unversioned(self):
2961
tree = self.make_branch_and_tree('tree')
2962
self.build_tree(['tree/removed'])
2963
tree.add('removed', 'removed-id')
2964
tt = TreeTransform(tree)
2965
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2966
self.assertMatchingIterEntries(tt)
2968
def test_iter_entries_by_dir_moved(self):
2969
tree = self.make_branch_and_tree('tree')
2970
self.build_tree(['tree/moved', 'tree/new_parent/'])
2971
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2972
tt = TreeTransform(tree)
2973
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2974
tt.trans_id_file_id('moved-id'))
2975
self.assertMatchingIterEntries(tt)
2977
def test_iter_entries_by_dir_specific_file_ids(self):
2978
tree = self.make_branch_and_tree('tree')
2979
tree.set_root_id('tree-root-id')
2980
self.build_tree(['tree/parent/', 'tree/parent/child'])
2981
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2982
tt = TreeTransform(tree)
2983
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2985
def test_symlink_content_summary(self):
2986
self.requireFeature(SymlinkFeature)
2987
preview = self.get_empty_preview()
2988
preview.new_symlink('path', preview.root, 'target', 'path-id')
2989
summary = preview.get_preview_tree().path_content_summary('path')
2990
self.assertEqual(('symlink', None, None, 'target'), summary)
2992
def test_missing_content_summary(self):
2993
preview = self.get_empty_preview()
2994
summary = preview.get_preview_tree().path_content_summary('path')
2995
self.assertEqual(('missing', None, None, None), summary)
2997
def test_deleted_content_summary(self):
2998
tree = self.make_branch_and_tree('tree')
2999
self.build_tree(['tree/path/'])
3001
preview = TransformPreview(tree)
3002
self.addCleanup(preview.finalize)
3003
preview.delete_contents(preview.trans_id_tree_path('path'))
3004
summary = preview.get_preview_tree().path_content_summary('path')
3005
self.assertEqual(('missing', None, None, None), summary)
3007
def test_file_content_summary_executable(self):
3008
preview = self.get_empty_preview()
3009
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
3010
preview.set_executability(True, path_id)
3011
summary = preview.get_preview_tree().path_content_summary('path')
3012
self.assertEqual(4, len(summary))
3013
self.assertEqual('file', summary[0])
3014
# size must be known
3015
self.assertEqual(len('contents'), summary[1])
3017
self.assertEqual(True, summary[2])
3018
# will not have hash (not cheap to determine)
3019
self.assertIs(None, summary[3])
3021
def test_change_executability(self):
3022
tree = self.make_branch_and_tree('tree')
3023
self.build_tree(['tree/path'])
3025
preview = TransformPreview(tree)
3026
self.addCleanup(preview.finalize)
3027
path_id = preview.trans_id_tree_path('path')
3028
preview.set_executability(True, path_id)
3029
summary = preview.get_preview_tree().path_content_summary('path')
3030
self.assertEqual(True, summary[2])
3032
def test_file_content_summary_non_exec(self):
3033
preview = self.get_empty_preview()
3034
preview.new_file('path', preview.root, 'contents', 'path-id')
3035
summary = preview.get_preview_tree().path_content_summary('path')
3036
self.assertEqual(4, len(summary))
3037
self.assertEqual('file', summary[0])
3038
# size must be known
3039
self.assertEqual(len('contents'), summary[1])
3041
self.assertEqual(False, summary[2])
3042
# will not have hash (not cheap to determine)
3043
self.assertIs(None, summary[3])
3045
def test_dir_content_summary(self):
3046
preview = self.get_empty_preview()
3047
preview.new_directory('path', preview.root, 'path-id')
3048
summary = preview.get_preview_tree().path_content_summary('path')
3049
self.assertEqual(('directory', None, None, None), summary)
3051
def test_tree_content_summary(self):
3052
preview = self.get_empty_preview()
3053
path = preview.new_directory('path', preview.root, 'path-id')
3054
preview.set_tree_reference('rev-1', path)
3055
summary = preview.get_preview_tree().path_content_summary('path')
3056
self.assertEqual(4, len(summary))
3057
self.assertEqual('tree-reference', summary[0])
3059
def test_annotate(self):
3060
tree = self.make_branch_and_tree('tree')
3061
self.build_tree_contents([('tree/file', 'a\n')])
3062
tree.add('file', 'file-id')
3063
tree.commit('a', rev_id='one')
3064
self.build_tree_contents([('tree/file', 'a\nb\n')])
3065
preview = TransformPreview(tree)
3066
self.addCleanup(preview.finalize)
3067
file_trans_id = preview.trans_id_file_id('file-id')
3068
preview.delete_contents(file_trans_id)
3069
preview.create_file('a\nb\nc\n', file_trans_id)
3070
preview_tree = preview.get_preview_tree()
3076
annotation = preview_tree.annotate_iter('file-id', 'me:')
3077
self.assertEqual(expected, annotation)
3079
def test_annotate_missing(self):
3080
preview = self.get_empty_preview()
3081
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3082
preview_tree = preview.get_preview_tree()
3088
annotation = preview_tree.annotate_iter('file-id', 'me:')
3089
self.assertEqual(expected, annotation)
3091
def test_annotate_rename(self):
3092
tree = self.make_branch_and_tree('tree')
3093
self.build_tree_contents([('tree/file', 'a\n')])
3094
tree.add('file', 'file-id')
3095
tree.commit('a', rev_id='one')
3096
preview = TransformPreview(tree)
3097
self.addCleanup(preview.finalize)
3098
file_trans_id = preview.trans_id_file_id('file-id')
3099
preview.adjust_path('newname', preview.root, file_trans_id)
3100
preview_tree = preview.get_preview_tree()
3104
annotation = preview_tree.annotate_iter('file-id', 'me:')
3105
self.assertEqual(expected, annotation)
3107
def test_annotate_deleted(self):
3108
tree = self.make_branch_and_tree('tree')
3109
self.build_tree_contents([('tree/file', 'a\n')])
3110
tree.add('file', 'file-id')
3111
tree.commit('a', rev_id='one')
3112
self.build_tree_contents([('tree/file', 'a\nb\n')])
3113
preview = TransformPreview(tree)
3114
self.addCleanup(preview.finalize)
3115
file_trans_id = preview.trans_id_file_id('file-id')
3116
preview.delete_contents(file_trans_id)
3117
preview_tree = preview.get_preview_tree()
3118
annotation = preview_tree.annotate_iter('file-id', 'me:')
3119
self.assertIs(None, annotation)
3121
def test_stored_kind(self):
3122
preview = self.get_empty_preview()
3123
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3124
preview_tree = preview.get_preview_tree()
3125
self.assertEqual('file', preview_tree.stored_kind('file-id'))
3127
def test_is_executable(self):
3128
preview = self.get_empty_preview()
3129
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3130
preview.set_executability(True, preview.trans_id_file_id('file-id'))
3131
preview_tree = preview.get_preview_tree()
3132
self.assertEqual(True, preview_tree.is_executable('file-id'))
3134
def test_get_set_parent_ids(self):
3135
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3136
self.assertEqual([], preview_tree.get_parent_ids())
3137
preview_tree.set_parent_ids(['rev-1'])
3138
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
3140
def test_plan_file_merge(self):
3141
work_a = self.make_branch_and_tree('wta')
3142
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3143
work_a.add('file', 'file-id')
3144
base_id = work_a.commit('base version')
3145
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3146
preview = TransformPreview(work_a)
3147
self.addCleanup(preview.finalize)
3148
trans_id = preview.trans_id_file_id('file-id')
3149
preview.delete_contents(trans_id)
3150
preview.create_file('b\nc\nd\ne\n', trans_id)
3151
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3152
tree_a = preview.get_preview_tree()
3153
tree_a.set_parent_ids([base_id])
3155
('killed-a', 'a\n'),
3156
('killed-b', 'b\n'),
3157
('unchanged', 'c\n'),
3158
('unchanged', 'd\n'),
3161
], list(tree_a.plan_file_merge('file-id', tree_b)))
3163
def test_plan_file_merge_revision_tree(self):
3164
work_a = self.make_branch_and_tree('wta')
3165
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3166
work_a.add('file', 'file-id')
3167
base_id = work_a.commit('base version')
3168
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3169
preview = TransformPreview(work_a.basis_tree())
3170
self.addCleanup(preview.finalize)
3171
trans_id = preview.trans_id_file_id('file-id')
3172
preview.delete_contents(trans_id)
3173
preview.create_file('b\nc\nd\ne\n', trans_id)
3174
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3175
tree_a = preview.get_preview_tree()
3176
tree_a.set_parent_ids([base_id])
3178
('killed-a', 'a\n'),
3179
('killed-b', 'b\n'),
3180
('unchanged', 'c\n'),
3181
('unchanged', 'd\n'),
3184
], list(tree_a.plan_file_merge('file-id', tree_b)))
3186
def test_walkdirs(self):
3187
preview = self.get_empty_preview()
3188
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3189
# FIXME: new_directory should mark root.
3190
preview.fixup_new_roots()
3191
preview_tree = preview.get_preview_tree()
3192
file_trans_id = preview.new_file('a', preview.root, 'contents',
3194
expected = [(('', 'tree-root'),
3195
[('a', 'a', 'file', None, 'a-id', 'file')])]
3196
self.assertEqual(expected, list(preview_tree.walkdirs()))
3198
def test_extras(self):
3199
work_tree = self.make_branch_and_tree('tree')
3200
self.build_tree(['tree/removed-file', 'tree/existing-file',
3201
'tree/not-removed-file'])
3202
work_tree.add(['removed-file', 'not-removed-file'])
3203
preview = TransformPreview(work_tree)
3204
self.addCleanup(preview.finalize)
3205
preview.new_file('new-file', preview.root, 'contents')
3206
preview.new_file('new-versioned-file', preview.root, 'contents',
3208
tree = preview.get_preview_tree()
3209
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3210
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
3213
def test_merge_into_preview(self):
3214
work_tree = self.make_branch_and_tree('tree')
3215
self.build_tree_contents([('tree/file','b\n')])
3216
work_tree.add('file', 'file-id')
3217
work_tree.commit('first commit')
3218
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
3219
self.build_tree_contents([('child/file','b\nc\n')])
3220
child_tree.commit('child commit')
3221
child_tree.lock_write()
3222
self.addCleanup(child_tree.unlock)
3223
work_tree.lock_write()
3224
self.addCleanup(work_tree.unlock)
3225
preview = TransformPreview(work_tree)
3226
self.addCleanup(preview.finalize)
3227
file_trans_id = preview.trans_id_file_id('file-id')
3228
preview.delete_contents(file_trans_id)
3229
preview.create_file('a\nb\n', file_trans_id)
3230
preview_tree = preview.get_preview_tree()
3231
merger = Merger.from_revision_ids(None, preview_tree,
3232
child_tree.branch.last_revision(),
3233
other_branch=child_tree.branch,
3234
tree_branch=work_tree.branch)
3235
merger.merge_type = Merge3Merger
3236
tt = merger.make_merger().make_preview_transform()
3237
self.addCleanup(tt.finalize)
3238
final_tree = tt.get_preview_tree()
3239
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3241
def test_merge_preview_into_workingtree(self):
3242
tree = self.make_branch_and_tree('tree')
3243
tree.set_root_id('TREE_ROOT')
3244
tt = TransformPreview(tree)
3245
self.addCleanup(tt.finalize)
3246
tt.new_file('name', tt.root, 'content', 'file-id')
3247
tree2 = self.make_branch_and_tree('tree2')
3248
tree2.set_root_id('TREE_ROOT')
3249
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3250
None, tree.basis_tree())
3251
merger.merge_type = Merge3Merger
3254
def test_merge_preview_into_workingtree_handles_conflicts(self):
3255
tree = self.make_branch_and_tree('tree')
3256
self.build_tree_contents([('tree/foo', 'bar')])
3257
tree.add('foo', 'foo-id')
3259
tt = TransformPreview(tree)
3260
self.addCleanup(tt.finalize)
3261
trans_id = tt.trans_id_file_id('foo-id')
3262
tt.delete_contents(trans_id)
3263
tt.create_file('baz', trans_id)
3264
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
3265
self.build_tree_contents([('tree2/foo', 'qux')])
3267
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3268
pb, tree.basis_tree())
3269
merger.merge_type = Merge3Merger
3272
def test_has_filename(self):
3273
wt = self.make_branch_and_tree('tree')
3274
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3275
tt = TransformPreview(wt)
3276
removed_id = tt.trans_id_tree_path('removed')
3277
tt.delete_contents(removed_id)
3278
tt.new_file('new', tt.root, 'contents')
3279
modified_id = tt.trans_id_tree_path('modified')
3280
tt.delete_contents(modified_id)
3281
tt.create_file('modified-contents', modified_id)
3282
self.addCleanup(tt.finalize)
3283
tree = tt.get_preview_tree()
3284
self.assertTrue(tree.has_filename('unmodified'))
3285
self.assertFalse(tree.has_filename('not-present'))
3286
self.assertFalse(tree.has_filename('removed'))
3287
self.assertTrue(tree.has_filename('new'))
3288
self.assertTrue(tree.has_filename('modified'))
3290
def test_is_executable(self):
3291
tree = self.make_branch_and_tree('tree')
3292
preview = TransformPreview(tree)
3293
self.addCleanup(preview.finalize)
3294
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3295
preview_tree = preview.get_preview_tree()
3296
self.assertEqual(False, preview_tree.is_executable('baz-id',
3298
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3300
def test_commit_preview_tree(self):
3301
tree = self.make_branch_and_tree('tree')
3302
rev_id = tree.commit('rev1')
3303
tree.branch.lock_write()
3304
self.addCleanup(tree.branch.unlock)
3305
tt = TransformPreview(tree)
3306
tt.new_file('file', tt.root, 'contents', 'file_id')
3307
self.addCleanup(tt.finalize)
3308
preview = tt.get_preview_tree()
3309
preview.set_parent_ids([rev_id])
3310
builder = tree.branch.get_commit_builder([rev_id])
3311
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3312
builder.finish_inventory()
3313
rev2_id = builder.commit('rev2')
3314
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3315
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3317
def test_ascii_limbo_paths(self):
3318
self.requireFeature(features.UnicodeFilenameFeature)
3319
branch = self.make_branch('any')
3320
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3321
tt = TransformPreview(tree)
3322
self.addCleanup(tt.finalize)
3323
foo_id = tt.new_directory('', ROOT_PARENT)
3324
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3325
limbo_path = tt._limbo_name(bar_id)
3326
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3329
class FakeSerializer(object):
3330
"""Serializer implementation that simply returns the input.
3332
The input is returned in the order used by pack.ContainerPushParser.
3335
def bytes_record(bytes, names):
3339
class TestSerializeTransform(tests.TestCaseWithTransport):
3341
_test_needs_features = [features.UnicodeFilenameFeature]
3343
def get_preview(self, tree=None):
3345
tree = self.make_branch_and_tree('tree')
3346
tt = TransformPreview(tree)
3347
self.addCleanup(tt.finalize)
3350
def assertSerializesTo(self, expected, tt):
3351
records = list(tt.serialize(FakeSerializer()))
3352
self.assertEqual(expected, records)
3355
def default_attribs():
3360
'_new_executability': {},
3362
'_tree_path_ids': {'': 'new-0'},
3364
'_removed_contents': [],
3365
'_non_present_ids': {},
3368
def make_records(self, attribs, contents):
3370
(((('attribs'),),), bencode.bencode(attribs))]
3371
records.extend([(((n, k),), c) for n, k, c in contents])
3374
def creation_records(self):
3375
attribs = self.default_attribs()
3376
attribs['_id_number'] = 3
3377
attribs['_new_name'] = {
3378
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3379
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3380
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3381
attribs['_new_executability'] = {'new-1': 1}
3383
('new-1', 'file', 'i 1\nbar\n'),
3384
('new-2', 'directory', ''),
3386
return self.make_records(attribs, contents)
3388
def test_serialize_creation(self):
3389
tt = self.get_preview()
3390
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3391
tt.new_directory('qux', tt.root, 'quxx')
3392
self.assertSerializesTo(self.creation_records(), tt)
3394
def test_deserialize_creation(self):
3395
tt = self.get_preview()
3396
tt.deserialize(iter(self.creation_records()))
3397
self.assertEqual(3, tt._id_number)
3398
self.assertEqual({'new-1': u'foo\u1234',
3399
'new-2': 'qux'}, tt._new_name)
3400
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3401
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3402
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3403
self.assertEqual({'new-1': True}, tt._new_executability)
3404
self.assertEqual({'new-1': 'file',
3405
'new-2': 'directory'}, tt._new_contents)
3406
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3408
foo_content = foo_limbo.read()
3411
self.assertEqual('bar', foo_content)
3413
def symlink_creation_records(self):
3414
attribs = self.default_attribs()
3415
attribs['_id_number'] = 2
3416
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3417
attribs['_new_parent'] = {'new-1': 'new-0'}
3418
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3419
return self.make_records(attribs, contents)
3421
def test_serialize_symlink_creation(self):
3422
self.requireFeature(features.SymlinkFeature)
3423
tt = self.get_preview()
3424
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3425
self.assertSerializesTo(self.symlink_creation_records(), tt)
3427
def test_deserialize_symlink_creation(self):
3428
self.requireFeature(features.SymlinkFeature)
3429
tt = self.get_preview()
3430
tt.deserialize(iter(self.symlink_creation_records()))
3431
abspath = tt._limbo_name('new-1')
3432
foo_content = osutils.readlink(abspath)
3433
self.assertEqual(u'bar\u1234', foo_content)
3435
def make_destruction_preview(self):
3436
tree = self.make_branch_and_tree('.')
3437
self.build_tree([u'foo\u1234', 'bar'])
3438
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3439
return self.get_preview(tree)
3441
def destruction_records(self):
3442
attribs = self.default_attribs()
3443
attribs['_id_number'] = 3
3444
attribs['_removed_id'] = ['new-1']
3445
attribs['_removed_contents'] = ['new-2']
3446
attribs['_tree_path_ids'] = {
3448
u'foo\u1234'.encode('utf-8'): 'new-1',
3451
return self.make_records(attribs, [])
3453
def test_serialize_destruction(self):
3454
tt = self.make_destruction_preview()
3455
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3456
tt.unversion_file(foo_trans_id)
3457
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3458
tt.delete_contents(bar_trans_id)
3459
self.assertSerializesTo(self.destruction_records(), tt)
3461
def test_deserialize_destruction(self):
3462
tt = self.make_destruction_preview()
3463
tt.deserialize(iter(self.destruction_records()))
3464
self.assertEqual({u'foo\u1234': 'new-1',
3466
'': tt.root}, tt._tree_path_ids)
3467
self.assertEqual({'new-1': u'foo\u1234',
3469
tt.root: ''}, tt._tree_id_paths)
3470
self.assertEqual(set(['new-1']), tt._removed_id)
3471
self.assertEqual(set(['new-2']), tt._removed_contents)
3473
def missing_records(self):
3474
attribs = self.default_attribs()
3475
attribs['_id_number'] = 2
3476
attribs['_non_present_ids'] = {
3478
return self.make_records(attribs, [])
3480
def test_serialize_missing(self):
3481
tt = self.get_preview()
3482
boo_trans_id = tt.trans_id_file_id('boo')
3483
self.assertSerializesTo(self.missing_records(), tt)
3485
def test_deserialize_missing(self):
3486
tt = self.get_preview()
3487
tt.deserialize(iter(self.missing_records()))
3488
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3490
def make_modification_preview(self):
3491
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3492
LINES_TWO = 'z\nbb\nx\ndd\n'
3493
tree = self.make_branch_and_tree('tree')
3494
self.build_tree_contents([('tree/file', LINES_ONE)])
3495
tree.add('file', 'file-id')
3496
return self.get_preview(tree), LINES_TWO
3498
def modification_records(self):
3499
attribs = self.default_attribs()
3500
attribs['_id_number'] = 2
3501
attribs['_tree_path_ids'] = {
3504
attribs['_removed_contents'] = ['new-1']
3505
contents = [('new-1', 'file',
3506
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3507
return self.make_records(attribs, contents)
3509
def test_serialize_modification(self):
3510
tt, LINES = self.make_modification_preview()
3511
trans_id = tt.trans_id_file_id('file-id')
3512
tt.delete_contents(trans_id)
3513
tt.create_file(LINES, trans_id)
3514
self.assertSerializesTo(self.modification_records(), tt)
3516
def test_deserialize_modification(self):
3517
tt, LINES = self.make_modification_preview()
3518
tt.deserialize(iter(self.modification_records()))
3519
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3521
def make_kind_change_preview(self):
3522
LINES = 'a\nb\nc\nd\n'
3523
tree = self.make_branch_and_tree('tree')
3524
self.build_tree(['tree/foo/'])
3525
tree.add('foo', 'foo-id')
3526
return self.get_preview(tree), LINES
3528
def kind_change_records(self):
3529
attribs = self.default_attribs()
3530
attribs['_id_number'] = 2
3531
attribs['_tree_path_ids'] = {
3534
attribs['_removed_contents'] = ['new-1']
3535
contents = [('new-1', 'file',
3536
'i 4\na\nb\nc\nd\n\n')]
3537
return self.make_records(attribs, contents)
3539
def test_serialize_kind_change(self):
3540
tt, LINES = self.make_kind_change_preview()
3541
trans_id = tt.trans_id_file_id('foo-id')
3542
tt.delete_contents(trans_id)
3543
tt.create_file(LINES, trans_id)
3544
self.assertSerializesTo(self.kind_change_records(), tt)
3546
def test_deserialize_kind_change(self):
3547
tt, LINES = self.make_kind_change_preview()
3548
tt.deserialize(iter(self.kind_change_records()))
3549
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3551
def make_add_contents_preview(self):
3552
LINES = 'a\nb\nc\nd\n'
3553
tree = self.make_branch_and_tree('tree')
3554
self.build_tree(['tree/foo'])
3556
os.unlink('tree/foo')
3557
return self.get_preview(tree), LINES
3559
def add_contents_records(self):
3560
attribs = self.default_attribs()
3561
attribs['_id_number'] = 2
3562
attribs['_tree_path_ids'] = {
3565
contents = [('new-1', 'file',
3566
'i 4\na\nb\nc\nd\n\n')]
3567
return self.make_records(attribs, contents)
3569
def test_serialize_add_contents(self):
3570
tt, LINES = self.make_add_contents_preview()
3571
trans_id = tt.trans_id_tree_path('foo')
3572
tt.create_file(LINES, trans_id)
3573
self.assertSerializesTo(self.add_contents_records(), tt)
3575
def test_deserialize_add_contents(self):
3576
tt, LINES = self.make_add_contents_preview()
3577
tt.deserialize(iter(self.add_contents_records()))
3578
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3580
def test_get_parents_lines(self):
3581
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3582
LINES_TWO = 'z\nbb\nx\ndd\n'
3583
tree = self.make_branch_and_tree('tree')
3584
self.build_tree_contents([('tree/file', LINES_ONE)])
3585
tree.add('file', 'file-id')
3586
tt = self.get_preview(tree)
3587
trans_id = tt.trans_id_tree_path('file')
3588
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3589
tt._get_parents_lines(trans_id))
3591
def test_get_parents_texts(self):
3592
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3593
LINES_TWO = 'z\nbb\nx\ndd\n'
3594
tree = self.make_branch_and_tree('tree')
3595
self.build_tree_contents([('tree/file', LINES_ONE)])
3596
tree.add('file', 'file-id')
3597
tt = self.get_preview(tree)
3598
trans_id = tt.trans_id_tree_path('file')
3599
self.assertEqual((LINES_ONE,),
3600
tt._get_parents_texts(trans_id))
3603
class TestOrphan(tests.TestCaseWithTransport):
3605
def test_no_orphan_for_transform_preview(self):
3606
tree = self.make_branch_and_tree('tree')
3607
tt = transform.TransformPreview(tree)
3608
self.addCleanup(tt.finalize)
3609
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3611
def _set_orphan_policy(self, wt, policy):
3612
wt.branch.get_config_stack().set('bzr.transform.orphan_policy',
3615
def _prepare_orphan(self, wt):
3616
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3617
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3618
wt.commit('add dir and file ignoring foo')
3619
tt = transform.TreeTransform(wt)
3620
self.addCleanup(tt.finalize)
3621
# dir and bar are deleted
3622
dir_tid = tt.trans_id_tree_path('dir')
3623
file_tid = tt.trans_id_tree_path('dir/file')
3624
orphan_tid = tt.trans_id_tree_path('dir/foo')
3625
tt.delete_contents(file_tid)
3626
tt.unversion_file(file_tid)
3627
tt.delete_contents(dir_tid)
3628
tt.unversion_file(dir_tid)
3629
# There should be a conflict because dir still contain foo
3630
raw_conflicts = tt.find_conflicts()
3631
self.assertLength(1, raw_conflicts)
3632
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3633
return tt, orphan_tid
3635
def test_new_orphan_created(self):
3636
wt = self.make_branch_and_tree('.')
3637
self._set_orphan_policy(wt, 'move')
3638
tt, orphan_tid = self._prepare_orphan(wt)
3641
warnings.append(args[0] % args[1:])
3642
self.overrideAttr(trace, 'warning', warning)
3643
remaining_conflicts = resolve_conflicts(tt)
3644
self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
3646
# Yeah for resolved conflicts !
3647
self.assertLength(0, remaining_conflicts)
3648
# We have a new orphan
3649
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3650
self.assertEquals('bzr-orphans',
3651
tt.final_name(tt.final_parent(orphan_tid)))
3653
def test_never_orphan(self):
3654
wt = self.make_branch_and_tree('.')
3655
self._set_orphan_policy(wt, 'conflict')
3656
tt, orphan_tid = self._prepare_orphan(wt)
3657
remaining_conflicts = resolve_conflicts(tt)
3658
self.assertLength(1, remaining_conflicts)
3659
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3660
remaining_conflicts.pop())
3662
def test_orphan_error(self):
3663
def bogus_orphan(tt, orphan_id, parent_id):
3664
raise transform.OrphaningError(tt.final_name(orphan_id),
3665
tt.final_name(parent_id))
3666
transform.orphaning_registry.register('bogus', bogus_orphan,
3667
'Raise an error when orphaning')
3668
wt = self.make_branch_and_tree('.')
3669
self._set_orphan_policy(wt, 'bogus')
3670
tt, orphan_tid = self._prepare_orphan(wt)
3671
remaining_conflicts = resolve_conflicts(tt)
3672
self.assertLength(1, remaining_conflicts)
3673
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3674
remaining_conflicts.pop())
3676
def test_unknown_orphan_policy(self):
3677
wt = self.make_branch_and_tree('.')
3678
# Set a fictional policy nobody ever implemented
3679
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3680
tt, orphan_tid = self._prepare_orphan(wt)
3683
warnings.append(args[0] % args[1:])
3684
self.overrideAttr(trace, 'warning', warning)
3685
remaining_conflicts = resolve_conflicts(tt)
3686
# We fallback to the default policy which create a conflict
3687
self.assertLength(1, remaining_conflicts)
3688
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3689
remaining_conflicts.pop())
3690
self.assertLength(1, warnings)
3691
self.assertStartsWith(warnings[0], 'Value "donttouchmypreciouuus" ')
3694
class TestTransformHooks(tests.TestCaseWithTransport):
3697
super(TestTransformHooks, self).setUp()
3698
self.wt = self.make_branch_and_tree('.')
3701
def get_transform(self):
3702
transform = TreeTransform(self.wt)
3703
self.addCleanup(transform.finalize)
3704
return transform, transform.root
3706
def test_pre_commit_hooks(self):
3708
def record_pre_transform(tree, tt):
3709
calls.append((tree, tt))
3710
MutableTree.hooks.install_named_hook('pre_transform',
3711
record_pre_transform, "Pre transform")
3712
transform, root = self.get_transform()
3713
old_root_id = transform.tree_file_id(root)
3715
self.assertEqual(old_root_id, self.wt.get_root_id())
3716
self.assertEquals([(self.wt, transform)], calls)
3718
def test_post_commit_hooks(self):
3720
def record_post_transform(tree, tt):
3721
calls.append((tree, tt))
3722
MutableTree.hooks.install_named_hook('post_transform',
3723
record_post_transform, "Post transform")
3724
transform, root = self.get_transform()
3725
old_root_id = transform.tree_file_id(root)
3727
self.assertEqual(old_root_id, self.wt.get_root_id())
3728
self.assertEquals([(self.wt, transform)], calls)