940
479
rename.set_executability(True, myfile)
943
def test_rename_fails(self):
944
self.requireFeature(features.not_running_as_root)
945
# see https://bugs.launchpad.net/bzr/+bug/491763
946
create, root_id = self.get_transform()
947
first_dir = create.new_directory('first-dir', root_id, 'first-id')
948
myfile = create.new_file('myfile', root_id, 'myfile-text',
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')
951
if os.name == "posix" and sys.platform != "cygwin":
952
# posix filesystems fail on renaming if the readonly bit is set
953
osutils.make_readonly(self.wt.abspath('first-dir'))
954
elif os.name == "nt":
955
# windows filesystems fail on renaming open files
956
self.addCleanup(file(self.wt.abspath('myfile')).close)
958
self.skip("Don't know how to force a permissions error on rename")
959
# now transform to rename
960
rename_transform, root_id = self.get_transform()
961
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
962
dir_id = rename_transform.trans_id_file_id('first-id')
963
rename_transform.adjust_path('newname', dir_id, file_trans_id)
964
e = self.assertRaises(errors.TransformRenameFailed,
965
rename_transform.apply)
967
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
968
# to .../first-dir/newname: [Errno 13] Permission denied"
969
# On windows looks like:
970
# "Failed to rename .../work/myfile to
971
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
972
# This test isn't concerned with exactly what the error looks like,
973
# and the strerror will vary across OS and locales, but the assert
974
# that the exeception attributes are what we expect
975
self.assertEqual(e.errno, errno.EACCES)
976
if os.name == "posix":
977
self.assertEndsWith(e.to_path, "/first-dir/newname")
979
self.assertEqual(os.path.basename(e.from_path), "myfile")
981
def test_set_executability_order(self):
982
"""Ensure that executability behaves the same, no matter what order.
984
- create file and set executability simultaneously
985
- create file and set executability afterward
986
- unsetting the executability of a file whose executability has not been
987
declared should throw an exception (this may happen when a
988
merge attempts to create a file with a duplicate ID)
990
transform, root = self.get_transform()
993
self.addCleanup(wt.unlock)
994
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
996
sac = transform.new_file('set_after_creation', root,
997
'Set after creation', 'sac')
998
transform.set_executability(True, sac)
999
uws = transform.new_file('unset_without_set', root, 'Unset badly',
1001
self.assertRaises(KeyError, transform.set_executability, None, uws)
1003
self.assertTrue(wt.is_executable('soc'))
1004
self.assertTrue(wt.is_executable('sac'))
1006
def test_preserve_mode(self):
1007
"""File mode is preserved when replacing content"""
1008
if sys.platform == 'win32':
1009
raise TestSkipped('chmod has no effect on win32')
1010
transform, root = self.get_transform()
1011
transform.new_file('file1', root, 'contents', 'file1-id', True)
1013
self.wt.lock_write()
1014
self.addCleanup(self.wt.unlock)
1015
self.assertTrue(self.wt.is_executable('file1-id'))
1016
transform, root = self.get_transform()
1017
file1_id = transform.trans_id_tree_file_id('file1-id')
1018
transform.delete_contents(file1_id)
1019
transform.create_file('contents2', file1_id)
1021
self.assertTrue(self.wt.is_executable('file1-id'))
1023
def test__set_mode_stats_correctly(self):
1024
"""_set_mode stats to determine file mode."""
1025
if sys.platform == 'win32':
1026
raise TestSkipped('chmod has no effect on win32')
1030
def instrumented_stat(path):
1031
stat_paths.append(path)
1032
return real_stat(path)
1034
transform, root = self.get_transform()
1036
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
1037
file_id='bar-id-1', executable=False)
1040
transform, root = self.get_transform()
1041
bar1_id = transform.trans_id_tree_path('bar')
1042
bar2_id = transform.trans_id_tree_path('bar2')
1044
os.stat = instrumented_stat
1045
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
1048
transform.finalize()
1050
bar1_abspath = self.wt.abspath('bar')
1051
self.assertEqual([bar1_abspath], stat_paths)
1053
def test_iter_changes(self):
1054
self.wt.set_root_id('eert_toor')
1055
transform, root = self.get_transform()
1056
transform.new_file('old', root, 'blah', 'id-1', True)
1058
transform, root = self.get_transform()
1060
self.assertEqual([], list(transform.iter_changes()))
1061
old = transform.trans_id_tree_file_id('id-1')
1062
transform.unversion_file(old)
1063
self.assertEqual([('id-1', ('old', None), False, (True, False),
1064
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1065
(True, True))], list(transform.iter_changes()))
1066
transform.new_directory('new', root, 'id-1')
1067
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
1068
('eert_toor', 'eert_toor'), ('old', 'new'),
1069
('file', 'directory'),
1070
(True, False))], list(transform.iter_changes()))
1072
transform.finalize()
1074
def test_iter_changes_new(self):
1075
self.wt.set_root_id('eert_toor')
1076
transform, root = self.get_transform()
1077
transform.new_file('old', root, 'blah')
1079
transform, root = self.get_transform()
1081
old = transform.trans_id_tree_path('old')
1082
transform.version_file('id-1', old)
1083
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
1084
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1085
(False, False))], list(transform.iter_changes()))
1087
transform.finalize()
1089
def test_iter_changes_modifications(self):
1090
self.wt.set_root_id('eert_toor')
1091
transform, root = self.get_transform()
1092
transform.new_file('old', root, 'blah', 'id-1')
1093
transform.new_file('new', root, 'blah')
1094
transform.new_directory('subdir', root, 'subdir-id')
1096
transform, root = self.get_transform()
1098
old = transform.trans_id_tree_path('old')
1099
subdir = transform.trans_id_tree_file_id('subdir-id')
1100
new = transform.trans_id_tree_path('new')
1101
self.assertEqual([], list(transform.iter_changes()))
1104
transform.delete_contents(old)
1105
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1106
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1107
(False, False))], list(transform.iter_changes()))
1110
transform.create_file('blah', old)
1111
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1112
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1113
(False, False))], list(transform.iter_changes()))
1114
transform.cancel_deletion(old)
1115
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1116
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1117
(False, False))], list(transform.iter_changes()))
1118
transform.cancel_creation(old)
1120
# move file_id to a different file
1121
self.assertEqual([], list(transform.iter_changes()))
1122
transform.unversion_file(old)
1123
transform.version_file('id-1', new)
1124
transform.adjust_path('old', root, new)
1125
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1126
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1127
(False, False))], list(transform.iter_changes()))
1128
transform.cancel_versioning(new)
1129
transform._removed_id = set()
1132
self.assertEqual([], list(transform.iter_changes()))
1133
transform.set_executability(True, old)
1134
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1135
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1136
(False, True))], list(transform.iter_changes()))
1137
transform.set_executability(None, old)
1140
self.assertEqual([], list(transform.iter_changes()))
1141
transform.adjust_path('new', root, old)
1142
transform._new_parent = {}
1143
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1144
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1145
(False, False))], list(transform.iter_changes()))
1146
transform._new_name = {}
1149
self.assertEqual([], list(transform.iter_changes()))
1150
transform.adjust_path('new', subdir, old)
1151
transform._new_name = {}
1152
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1153
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1154
('file', 'file'), (False, False))],
1155
list(transform.iter_changes()))
1156
transform._new_path = {}
1159
transform.finalize()
1161
def test_iter_changes_modified_bleed(self):
1162
self.wt.set_root_id('eert_toor')
1163
"""Modified flag should not bleed from one change to another"""
1164
# unfortunately, we have no guarantee that file1 (which is modified)
1165
# will be applied before file2. And if it's applied after file2, it
1166
# obviously can't bleed into file2's change output. But for now, it
1168
transform, root = self.get_transform()
1169
transform.new_file('file1', root, 'blah', 'id-1')
1170
transform.new_file('file2', root, 'blah', 'id-2')
1172
transform, root = self.get_transform()
1174
transform.delete_contents(transform.trans_id_file_id('id-1'))
1175
transform.set_executability(True,
1176
transform.trans_id_file_id('id-2'))
1177
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1178
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1179
('file', None), (False, False)),
1180
('id-2', (u'file2', u'file2'), False, (True, True),
1181
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1182
('file', 'file'), (False, True))],
1183
list(transform.iter_changes()))
1185
transform.finalize()
1187
def test_iter_changes_move_missing(self):
1188
"""Test moving ids with no files around"""
1189
self.wt.set_root_id('toor_eert')
1190
# Need two steps because versioning a non-existant file is a conflict.
1191
transform, root = self.get_transform()
1192
transform.new_directory('floater', root, 'floater-id')
1194
transform, root = self.get_transform()
1195
transform.delete_contents(transform.trans_id_tree_path('floater'))
1197
transform, root = self.get_transform()
1198
floater = transform.trans_id_tree_path('floater')
1200
transform.adjust_path('flitter', root, floater)
1201
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1202
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1203
(None, None), (False, False))], list(transform.iter_changes()))
1205
transform.finalize()
1207
def test_iter_changes_pointless(self):
1208
"""Ensure that no-ops are not treated as modifications"""
1209
self.wt.set_root_id('eert_toor')
1210
transform, root = self.get_transform()
1211
transform.new_file('old', root, 'blah', 'id-1')
1212
transform.new_directory('subdir', root, 'subdir-id')
1214
transform, root = self.get_transform()
1216
old = transform.trans_id_tree_path('old')
1217
subdir = transform.trans_id_tree_file_id('subdir-id')
1218
self.assertEqual([], list(transform.iter_changes()))
1219
transform.delete_contents(subdir)
1220
transform.create_directory(subdir)
1221
transform.set_executability(False, old)
1222
transform.unversion_file(old)
1223
transform.version_file('id-1', old)
1224
transform.adjust_path('old', root, old)
1225
self.assertEqual([], list(transform.iter_changes()))
1227
transform.finalize()
1229
def test_rename_count(self):
1230
transform, root = self.get_transform()
1231
transform.new_file('name1', root, 'contents')
1232
self.assertEqual(transform.rename_count, 0)
1234
self.assertEqual(transform.rename_count, 1)
1235
transform2, root = self.get_transform()
1236
transform2.adjust_path('name2', root,
1237
transform2.trans_id_tree_path('name1'))
1238
self.assertEqual(transform2.rename_count, 0)
1240
self.assertEqual(transform2.rename_count, 2)
1242
def test_change_parent(self):
1243
"""Ensure that after we change a parent, the results are still right.
1245
Renames and parent changes on pending transforms can happen as part
1246
of conflict resolution, and are explicitly permitted by the
1249
This test ensures they work correctly with the rename-avoidance
1252
transform, root = self.get_transform()
1253
parent1 = transform.new_directory('parent1', root)
1254
child1 = transform.new_file('child1', parent1, 'contents')
1255
parent2 = transform.new_directory('parent2', root)
1256
transform.adjust_path('child1', parent2, child1)
1258
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1259
self.assertPathExists(self.wt.abspath('parent2/child1'))
1260
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1261
# no rename for child1 (counting only renames during apply)
1262
self.assertEqual(2, transform.rename_count)
1264
def test_cancel_parent(self):
1265
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1267
This is like the test_change_parent, except that we cancel the parent
1268
before adjusting the path. The transform must detect that the
1269
directory is non-empty, and move children to safe locations.
1271
transform, root = self.get_transform()
1272
parent1 = transform.new_directory('parent1', root)
1273
child1 = transform.new_file('child1', parent1, 'contents')
1274
child2 = transform.new_file('child2', parent1, 'contents')
1276
transform.cancel_creation(parent1)
1278
self.fail('Failed to move child1 before deleting parent1')
1279
transform.cancel_creation(child2)
1280
transform.create_directory(parent1)
1282
transform.cancel_creation(parent1)
1283
# If the transform incorrectly believes that child2 is still in
1284
# parent1's limbo directory, it will try to rename it and fail
1285
# because was already moved by the first cancel_creation.
1287
self.fail('Transform still thinks child2 is a child of parent1')
1288
parent2 = transform.new_directory('parent2', root)
1289
transform.adjust_path('child1', parent2, child1)
1291
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1292
self.assertPathExists(self.wt.abspath('parent2/child1'))
1293
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1294
self.assertEqual(2, transform.rename_count)
1296
def test_adjust_and_cancel(self):
1297
"""Make sure adjust_path keeps track of limbo children properly"""
1298
transform, root = self.get_transform()
1299
parent1 = transform.new_directory('parent1', root)
1300
child1 = transform.new_file('child1', parent1, 'contents')
1301
parent2 = transform.new_directory('parent2', root)
1302
transform.adjust_path('child1', parent2, child1)
1303
transform.cancel_creation(child1)
1305
transform.cancel_creation(parent1)
1306
# if the transform thinks child1 is still in parent1's limbo
1307
# directory, it will attempt to move it and fail.
1309
self.fail('Transform still thinks child1 is a child of parent1')
1310
transform.finalize()
1312
def test_noname_contents(self):
1313
"""TreeTransform should permit deferring naming files."""
1314
transform, root = self.get_transform()
1315
parent = transform.trans_id_file_id('parent-id')
1317
transform.create_directory(parent)
1319
self.fail("Can't handle contents with no name")
1320
transform.finalize()
1322
def test_noname_contents_nested(self):
1323
"""TreeTransform should permit deferring naming files."""
1324
transform, root = self.get_transform()
1325
parent = transform.trans_id_file_id('parent-id')
1327
transform.create_directory(parent)
1329
self.fail("Can't handle contents with no name")
1330
child = transform.new_directory('child', parent)
1331
transform.adjust_path('parent', root, parent)
1333
self.assertPathExists(self.wt.abspath('parent/child'))
1334
self.assertEqual(1, transform.rename_count)
1336
def test_reuse_name(self):
1337
"""Avoid reusing the same limbo name for different files"""
1338
transform, root = self.get_transform()
1339
parent = transform.new_directory('parent', root)
1340
child1 = transform.new_directory('child', parent)
1342
child2 = transform.new_directory('child', parent)
1344
self.fail('Tranform tried to use the same limbo name twice')
1345
transform.adjust_path('child2', parent, child2)
1347
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1348
# child2 is put into top-level limbo because child1 has already
1349
# claimed the direct limbo path when child2 is created. There is no
1350
# advantage in renaming files once they're in top-level limbo, except
1352
self.assertEqual(2, transform.rename_count)
1354
def test_reuse_when_first_moved(self):
1355
"""Don't avoid direct paths when it is safe to use them"""
1356
transform, root = self.get_transform()
1357
parent = transform.new_directory('parent', root)
1358
child1 = transform.new_directory('child', parent)
1359
transform.adjust_path('child1', parent, child1)
1360
child2 = transform.new_directory('child', parent)
1362
# limbo/new-1 => parent
1363
self.assertEqual(1, transform.rename_count)
1365
def test_reuse_after_cancel(self):
1366
"""Don't avoid direct paths when it is safe to use them"""
1367
transform, root = self.get_transform()
1368
parent2 = transform.new_directory('parent2', root)
1369
child1 = transform.new_directory('child1', parent2)
1370
transform.cancel_creation(parent2)
1371
transform.create_directory(parent2)
1372
child2 = transform.new_directory('child1', parent2)
1373
transform.adjust_path('child2', parent2, child1)
1375
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1376
self.assertEqual(2, transform.rename_count)
1378
def test_finalize_order(self):
1379
"""Finalize must be done in child-to-parent order"""
1380
transform, root = self.get_transform()
1381
parent = transform.new_directory('parent', root)
1382
child = transform.new_directory('child', parent)
1384
transform.finalize()
1386
self.fail('Tried to remove parent before child1')
1388
def test_cancel_with_cancelled_child_should_succeed(self):
1389
transform, root = self.get_transform()
1390
parent = transform.new_directory('parent', root)
1391
child = transform.new_directory('child', parent)
1392
transform.cancel_creation(child)
1393
transform.cancel_creation(parent)
1394
transform.finalize()
1396
def test_rollback_on_directory_clash(self):
1398
wt = self.make_branch_and_tree('.')
1399
tt = TreeTransform(wt) # TreeTransform obtains write lock
1401
foo = tt.new_directory('foo', tt.root)
1402
tt.new_file('bar', foo, 'foobar')
1403
baz = tt.new_directory('baz', tt.root)
1404
tt.new_file('qux', baz, 'quux')
1405
# Ask for a rename 'foo' -> 'baz'
1406
tt.adjust_path('baz', tt.root, foo)
1407
# Lie to tt that we've already resolved all conflicts.
1408
tt.apply(no_conflicts=True)
1412
# The rename will fail because the target directory is not empty (but
1413
# raises FileExists anyway).
1414
err = self.assertRaises(errors.FileExists, tt_helper)
1415
self.assertContainsRe(str(err),
1416
"^File exists: .+/baz")
1418
def test_two_directories_clash(self):
1420
wt = self.make_branch_and_tree('.')
1421
tt = TreeTransform(wt) # TreeTransform obtains write lock
1423
foo_1 = tt.new_directory('foo', tt.root)
1424
tt.new_directory('bar', foo_1)
1425
# Adding the same directory with a different content
1426
foo_2 = tt.new_directory('foo', tt.root)
1427
tt.new_directory('baz', foo_2)
1428
# Lie to tt that we've already resolved all conflicts.
1429
tt.apply(no_conflicts=True)
1433
err = self.assertRaises(errors.FileExists, tt_helper)
1434
self.assertContainsRe(str(err),
1435
"^File exists: .+/foo")
1437
def test_two_directories_clash_finalize(self):
1439
wt = self.make_branch_and_tree('.')
1440
tt = TreeTransform(wt) # TreeTransform obtains write lock
1442
foo_1 = tt.new_directory('foo', tt.root)
1443
tt.new_directory('bar', foo_1)
1444
# Adding the same directory with a different content
1445
foo_2 = tt.new_directory('foo', tt.root)
1446
tt.new_directory('baz', foo_2)
1447
# Lie to tt that we've already resolved all conflicts.
1448
tt.apply(no_conflicts=True)
1452
err = self.assertRaises(errors.FileExists, tt_helper)
1453
self.assertContainsRe(str(err),
1454
"^File exists: .+/foo")
1456
def test_file_to_directory(self):
1457
wt = self.make_branch_and_tree('.')
1458
self.build_tree(['foo'])
1461
tt = TreeTransform(wt)
1462
self.addCleanup(tt.finalize)
1463
foo_trans_id = tt.trans_id_tree_path("foo")
1464
tt.delete_contents(foo_trans_id)
1465
tt.create_directory(foo_trans_id)
1466
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1467
tt.create_file(["aa\n"], bar_trans_id)
1468
tt.version_file("bar-1", bar_trans_id)
1470
self.assertPathExists("foo/bar")
1473
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1478
changes = wt.changes_from(wt.basis_tree())
1479
self.assertFalse(changes.has_changed(), changes)
1481
def test_file_to_symlink(self):
1482
self.requireFeature(SymlinkFeature)
1483
wt = self.make_branch_and_tree('.')
1484
self.build_tree(['foo'])
1487
tt = TreeTransform(wt)
1488
self.addCleanup(tt.finalize)
1489
foo_trans_id = tt.trans_id_tree_path("foo")
1490
tt.delete_contents(foo_trans_id)
1491
tt.create_symlink("bar", foo_trans_id)
1493
self.assertPathExists("foo")
1495
self.addCleanup(wt.unlock)
1496
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1499
def test_dir_to_file(self):
1500
wt = self.make_branch_and_tree('.')
1501
self.build_tree(['foo/', 'foo/bar'])
1502
wt.add(['foo', 'foo/bar'])
1504
tt = TreeTransform(wt)
1505
self.addCleanup(tt.finalize)
1506
foo_trans_id = tt.trans_id_tree_path("foo")
1507
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1508
tt.delete_contents(foo_trans_id)
1509
tt.delete_versioned(bar_trans_id)
1510
tt.create_file(["aa\n"], foo_trans_id)
1512
self.assertPathExists("foo")
1514
self.addCleanup(wt.unlock)
1515
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1518
def test_dir_to_hardlink(self):
1519
self.requireFeature(HardlinkFeature)
1520
wt = self.make_branch_and_tree('.')
1521
self.build_tree(['foo/', 'foo/bar'])
1522
wt.add(['foo', 'foo/bar'])
1524
tt = TreeTransform(wt)
1525
self.addCleanup(tt.finalize)
1526
foo_trans_id = tt.trans_id_tree_path("foo")
1527
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1528
tt.delete_contents(foo_trans_id)
1529
tt.delete_versioned(bar_trans_id)
1530
self.build_tree(['baz'])
1531
tt.create_hardlink("baz", foo_trans_id)
1533
self.assertPathExists("foo")
1534
self.assertPathExists("baz")
1536
self.addCleanup(wt.unlock)
1537
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1540
def test_no_final_path(self):
1541
transform, root = self.get_transform()
1542
trans_id = transform.trans_id_file_id('foo')
1543
transform.create_file('bar', trans_id)
1544
transform.cancel_creation(trans_id)
1547
def test_create_from_tree(self):
1548
tree1 = self.make_branch_and_tree('tree1')
1549
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1550
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1551
tree2 = self.make_branch_and_tree('tree2')
1552
tt = TreeTransform(tree2)
1553
foo_trans_id = tt.create_path('foo', tt.root)
1554
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1555
bar_trans_id = tt.create_path('bar', tt.root)
1556
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1558
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1559
self.assertFileEqual('baz', 'tree2/bar')
1561
def test_create_from_tree_bytes(self):
1562
"""Provided lines are used instead of tree content."""
1563
tree1 = self.make_branch_and_tree('tree1')
1564
self.build_tree_contents([('tree1/foo', 'bar'),])
1565
tree1.add('foo', 'foo-id')
1566
tree2 = self.make_branch_and_tree('tree2')
1567
tt = TreeTransform(tree2)
1568
foo_trans_id = tt.create_path('foo', tt.root)
1569
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1571
self.assertFileEqual('qux', 'tree2/foo')
1573
def test_create_from_tree_symlink(self):
1574
self.requireFeature(SymlinkFeature)
1575
tree1 = self.make_branch_and_tree('tree1')
1576
os.symlink('bar', 'tree1/foo')
1577
tree1.add('foo', 'foo-id')
1578
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1579
foo_trans_id = tt.create_path('foo', tt.root)
1580
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1582
self.assertEqual('bar', os.readlink('tree2/foo'))
488
self.assertEqual(find_interesting(wt, wt, ['vfile']),
490
self.assertRaises(NotVersionedError, find_interesting, wt, wt,
1585
494
class TransformGroup(object):
1587
def __init__(self, dirname, root_id):
495
def __init__(self, dirname):
1588
496
self.name = dirname
1589
497
os.mkdir(dirname)
1590
498
self.wt = BzrDir.create_standalone_workingtree(dirname)
1591
self.wt.set_root_id(root_id)
1592
499
self.b = self.wt.branch
1593
500
self.tt = TreeTransform(self.wt)
1594
501
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1597
503
def conflict_text(tree, merge):
1598
504
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1599
505
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1602
508
class TestTransformMerge(TestCaseInTempDir):
1604
509
def test_text_merge(self):
1605
root_id = generate_ids.gen_root_id()
1606
base = TransformGroup("base", root_id)
510
base = TransformGroup("base")
1607
511
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
1608
512
base.tt.new_file('b', base.root, 'b1', 'b')
1609
513
base.tt.new_file('c', base.root, 'c', 'c')
1798
698
a.add(['foo', 'foo/bar', 'foo/baz'])
1799
699
a.commit('initial commit')
1800
700
b = BzrDir.create_standalone_workingtree('b')
1801
basis = a.basis_tree()
1803
self.addCleanup(basis.unlock)
1804
build_tree(basis, b)
701
build_tree(a.basis_tree(), b)
1805
702
self.assertIs(os.path.isdir('b/foo'), True)
1806
703
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1807
704
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1809
def test_build_with_references(self):
1810
tree = self.make_branch_and_tree('source',
1811
format='dirstate-with-subtree')
1812
subtree = self.make_branch_and_tree('source/subtree',
1813
format='dirstate-with-subtree')
1814
tree.add_reference(subtree)
1815
tree.commit('a revision')
1816
tree.branch.create_checkout('target')
1817
self.assertPathExists('target')
1818
self.assertPathExists('target/subtree')
1820
def test_file_conflict_handling(self):
1821
"""Ensure that when building trees, conflict handling is done"""
1822
source = self.make_branch_and_tree('source')
1823
target = self.make_branch_and_tree('target')
1824
self.build_tree(['source/file', 'target/file'])
1825
source.add('file', 'new-file')
1826
source.commit('added file')
1827
build_tree(source.basis_tree(), target)
1828
self.assertEqual([DuplicateEntry('Moved existing file to',
1829
'file.moved', 'file', None, 'new-file')],
1831
target2 = self.make_branch_and_tree('target2')
1832
target_file = file('target2/file', 'wb')
1834
source_file = file('source/file', 'rb')
1836
target_file.write(source_file.read())
1841
build_tree(source.basis_tree(), target2)
1842
self.assertEqual([], target2.conflicts())
1844
def test_symlink_conflict_handling(self):
1845
"""Ensure that when building trees, conflict handling is done"""
1846
self.requireFeature(SymlinkFeature)
1847
source = self.make_branch_and_tree('source')
1848
os.symlink('foo', 'source/symlink')
1849
source.add('symlink', 'new-symlink')
1850
source.commit('added file')
1851
target = self.make_branch_and_tree('target')
1852
os.symlink('bar', 'target/symlink')
1853
build_tree(source.basis_tree(), target)
1854
self.assertEqual([DuplicateEntry('Moved existing file to',
1855
'symlink.moved', 'symlink', None, 'new-symlink')],
1857
target = self.make_branch_and_tree('target2')
1858
os.symlink('foo', 'target2/symlink')
1859
build_tree(source.basis_tree(), target)
1860
self.assertEqual([], target.conflicts())
1862
def test_directory_conflict_handling(self):
1863
"""Ensure that when building trees, conflict handling is done"""
1864
source = self.make_branch_and_tree('source')
1865
target = self.make_branch_and_tree('target')
1866
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1867
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1868
source.commit('added file')
1869
build_tree(source.basis_tree(), target)
1870
self.assertEqual([], target.conflicts())
1871
self.assertPathExists('target/dir1/file')
1873
# Ensure contents are merged
1874
target = self.make_branch_and_tree('target2')
1875
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1876
build_tree(source.basis_tree(), target)
1877
self.assertEqual([], target.conflicts())
1878
self.assertPathExists('target2/dir1/file2')
1879
self.assertPathExists('target2/dir1/file')
1881
# Ensure new contents are suppressed for existing branches
1882
target = self.make_branch_and_tree('target3')
1883
self.make_branch('target3/dir1')
1884
self.build_tree(['target3/dir1/file2'])
1885
build_tree(source.basis_tree(), target)
1886
self.assertPathDoesNotExist('target3/dir1/file')
1887
self.assertPathExists('target3/dir1/file2')
1888
self.assertPathExists('target3/dir1.diverted/file')
1889
self.assertEqual([DuplicateEntry('Diverted to',
1890
'dir1.diverted', 'dir1', 'new-dir1', None)],
1893
target = self.make_branch_and_tree('target4')
1894
self.build_tree(['target4/dir1/'])
1895
self.make_branch('target4/dir1/file')
1896
build_tree(source.basis_tree(), target)
1897
self.assertPathExists('target4/dir1/file')
1898
self.assertEqual('directory', file_kind('target4/dir1/file'))
1899
self.assertPathExists('target4/dir1/file.diverted')
1900
self.assertEqual([DuplicateEntry('Diverted to',
1901
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1904
def test_mixed_conflict_handling(self):
1905
"""Ensure that when building trees, conflict handling is done"""
1906
source = self.make_branch_and_tree('source')
1907
target = self.make_branch_and_tree('target')
1908
self.build_tree(['source/name', 'target/name/'])
1909
source.add('name', 'new-name')
1910
source.commit('added file')
1911
build_tree(source.basis_tree(), target)
1912
self.assertEqual([DuplicateEntry('Moved existing file to',
1913
'name.moved', 'name', None, 'new-name')], target.conflicts())
1915
def test_raises_in_populated(self):
1916
source = self.make_branch_and_tree('source')
1917
self.build_tree(['source/name'])
1919
source.commit('added name')
1920
target = self.make_branch_and_tree('target')
1921
self.build_tree(['target/name'])
1923
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1924
build_tree, source.basis_tree(), target)
1926
def test_build_tree_rename_count(self):
1927
source = self.make_branch_and_tree('source')
1928
self.build_tree(['source/file1', 'source/dir1/'])
1929
source.add(['file1', 'dir1'])
1930
source.commit('add1')
1931
target1 = self.make_branch_and_tree('target1')
1932
transform_result = build_tree(source.basis_tree(), target1)
1933
self.assertEqual(2, transform_result.rename_count)
1935
self.build_tree(['source/dir1/file2'])
1936
source.add(['dir1/file2'])
1937
source.commit('add3')
1938
target2 = self.make_branch_and_tree('target2')
1939
transform_result = build_tree(source.basis_tree(), target2)
1940
# children of non-root directories should not be renamed
1941
self.assertEqual(2, transform_result.rename_count)
1943
def create_ab_tree(self):
1944
"""Create a committed test tree with two files"""
1945
source = self.make_branch_and_tree('source')
1946
self.build_tree_contents([('source/file1', 'A')])
1947
self.build_tree_contents([('source/file2', 'B')])
1948
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1949
source.commit('commit files')
1951
self.addCleanup(source.unlock)
1954
def test_build_tree_accelerator_tree(self):
1955
source = self.create_ab_tree()
1956
self.build_tree_contents([('source/file2', 'C')])
1958
real_source_get_file = source.get_file
1959
def get_file(file_id, path=None):
1960
calls.append(file_id)
1961
return real_source_get_file(file_id, path)
1962
source.get_file = get_file
1963
target = self.make_branch_and_tree('target')
1964
revision_tree = source.basis_tree()
1965
revision_tree.lock_read()
1966
self.addCleanup(revision_tree.unlock)
1967
build_tree(revision_tree, target, source)
1968
self.assertEqual(['file1-id'], calls)
1970
self.addCleanup(target.unlock)
1971
self.assertEqual([], list(target.iter_changes(revision_tree)))
1973
def test_build_tree_accelerator_tree_observes_sha1(self):
1974
source = self.create_ab_tree()
1975
sha1 = osutils.sha_string('A')
1976
target = self.make_branch_and_tree('target')
1978
self.addCleanup(target.unlock)
1979
state = target.current_dirstate()
1980
state._cutoff_time = time.time() + 60
1981
build_tree(source.basis_tree(), target, source)
1982
entry = state._get_entry(0, path_utf8='file1')
1983
self.assertEqual(sha1, entry[1][0][1])
1985
def test_build_tree_accelerator_tree_missing_file(self):
1986
source = self.create_ab_tree()
1987
os.unlink('source/file1')
1988
source.remove(['file2'])
1989
target = self.make_branch_and_tree('target')
1990
revision_tree = source.basis_tree()
1991
revision_tree.lock_read()
1992
self.addCleanup(revision_tree.unlock)
1993
build_tree(revision_tree, target, source)
1995
self.addCleanup(target.unlock)
1996
self.assertEqual([], list(target.iter_changes(revision_tree)))
1998
def test_build_tree_accelerator_wrong_kind(self):
1999
self.requireFeature(SymlinkFeature)
2000
source = self.make_branch_and_tree('source')
2001
self.build_tree_contents([('source/file1', '')])
2002
self.build_tree_contents([('source/file2', '')])
2003
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2004
source.commit('commit files')
2005
os.unlink('source/file2')
2006
self.build_tree_contents([('source/file2/', 'C')])
2007
os.unlink('source/file1')
2008
os.symlink('file2', 'source/file1')
2010
real_source_get_file = source.get_file
2011
def get_file(file_id, path=None):
2012
calls.append(file_id)
2013
return real_source_get_file(file_id, path)
2014
source.get_file = get_file
2015
target = self.make_branch_and_tree('target')
2016
revision_tree = source.basis_tree()
2017
revision_tree.lock_read()
2018
self.addCleanup(revision_tree.unlock)
2019
build_tree(revision_tree, target, source)
2020
self.assertEqual([], calls)
2022
self.addCleanup(target.unlock)
2023
self.assertEqual([], list(target.iter_changes(revision_tree)))
2025
def test_build_tree_hardlink(self):
2026
self.requireFeature(HardlinkFeature)
2027
source = self.create_ab_tree()
2028
target = self.make_branch_and_tree('target')
2029
revision_tree = source.basis_tree()
2030
revision_tree.lock_read()
2031
self.addCleanup(revision_tree.unlock)
2032
build_tree(revision_tree, target, source, hardlink=True)
2034
self.addCleanup(target.unlock)
2035
self.assertEqual([], list(target.iter_changes(revision_tree)))
2036
source_stat = os.stat('source/file1')
2037
target_stat = os.stat('target/file1')
2038
self.assertEqual(source_stat, target_stat)
2040
# Explicitly disallowing hardlinks should prevent them.
2041
target2 = self.make_branch_and_tree('target2')
2042
build_tree(revision_tree, target2, source, hardlink=False)
2044
self.addCleanup(target2.unlock)
2045
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2046
source_stat = os.stat('source/file1')
2047
target2_stat = os.stat('target2/file1')
2048
self.assertNotEqual(source_stat, target2_stat)
2050
def test_build_tree_accelerator_tree_moved(self):
2051
source = self.make_branch_and_tree('source')
2052
self.build_tree_contents([('source/file1', 'A')])
2053
source.add(['file1'], ['file1-id'])
2054
source.commit('commit files')
2055
source.rename_one('file1', 'file2')
2057
self.addCleanup(source.unlock)
2058
target = self.make_branch_and_tree('target')
2059
revision_tree = source.basis_tree()
2060
revision_tree.lock_read()
2061
self.addCleanup(revision_tree.unlock)
2062
build_tree(revision_tree, target, source)
2064
self.addCleanup(target.unlock)
2065
self.assertEqual([], list(target.iter_changes(revision_tree)))
2067
def test_build_tree_hardlinks_preserve_execute(self):
2068
self.requireFeature(HardlinkFeature)
2069
source = self.create_ab_tree()
2070
tt = TreeTransform(source)
2071
trans_id = tt.trans_id_tree_file_id('file1-id')
2072
tt.set_executability(True, trans_id)
2074
self.assertTrue(source.is_executable('file1-id'))
2075
target = self.make_branch_and_tree('target')
2076
revision_tree = source.basis_tree()
2077
revision_tree.lock_read()
2078
self.addCleanup(revision_tree.unlock)
2079
build_tree(revision_tree, target, source, hardlink=True)
2081
self.addCleanup(target.unlock)
2082
self.assertEqual([], list(target.iter_changes(revision_tree)))
2083
self.assertTrue(source.is_executable('file1-id'))
2085
def install_rot13_content_filter(self, pattern):
2087
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2088
# below, but that looks a bit... hard to read even if it's exactly
2090
original_registry = filters._reset_registry()
2091
def restore_registry():
2092
filters._reset_registry(original_registry)
2093
self.addCleanup(restore_registry)
2094
def rot13(chunks, context=None):
2095
return [''.join(chunks).encode('rot13')]
2096
rot13filter = filters.ContentFilter(rot13, rot13)
2097
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
2098
os.mkdir(self.test_home_dir + '/.bazaar')
2099
rules_filename = self.test_home_dir + '/.bazaar/rules'
2100
f = open(rules_filename, 'wb')
2101
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2103
def uninstall_rules():
2104
os.remove(rules_filename)
2106
self.addCleanup(uninstall_rules)
2109
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2110
"""build_tree will not hardlink files that have content filtering rules
2111
applied to them (but will still hardlink other files from the same tree
2114
self.requireFeature(HardlinkFeature)
2115
self.install_rot13_content_filter('file1')
2116
source = self.create_ab_tree()
2117
target = self.make_branch_and_tree('target')
2118
revision_tree = source.basis_tree()
2119
revision_tree.lock_read()
2120
self.addCleanup(revision_tree.unlock)
2121
build_tree(revision_tree, target, source, hardlink=True)
2123
self.addCleanup(target.unlock)
2124
self.assertEqual([], list(target.iter_changes(revision_tree)))
2125
source_stat = os.stat('source/file1')
2126
target_stat = os.stat('target/file1')
2127
self.assertNotEqual(source_stat, target_stat)
2128
source_stat = os.stat('source/file2')
2129
target_stat = os.stat('target/file2')
2130
self.assertEqualStat(source_stat, target_stat)
2132
def test_case_insensitive_build_tree_inventory(self):
2133
if (tests.CaseInsensitiveFilesystemFeature.available()
2134
or tests.CaseInsCasePresFilenameFeature.available()):
2135
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2136
source = self.make_branch_and_tree('source')
2137
self.build_tree(['source/file', 'source/FILE'])
2138
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2139
source.commit('added files')
2140
# Don't try this at home, kids!
2141
# Force the tree to report that it is case insensitive
2142
target = self.make_branch_and_tree('target')
2143
target.case_sensitive = False
2144
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2145
self.assertEqual('file.moved', target.id2path('lower-id'))
2146
self.assertEqual('FILE', target.id2path('upper-id'))
2148
def test_build_tree_observes_sha(self):
2149
source = self.make_branch_and_tree('source')
2150
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2151
source.add(['file1', 'dir', 'dir/file2'],
2152
['file1-id', 'dir-id', 'file2-id'])
2153
source.commit('new files')
2154
target = self.make_branch_and_tree('target')
2156
self.addCleanup(target.unlock)
2157
# We make use of the fact that DirState caches its cutoff time. So we
2158
# set the 'safe' time to one minute in the future.
2159
state = target.current_dirstate()
2160
state._cutoff_time = time.time() + 60
2161
build_tree(source.basis_tree(), target)
2162
entry1_sha = osutils.sha_file_by_name('source/file1')
2163
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2164
# entry[1] is the state information, entry[1][0] is the state of the
2165
# working tree, entry[1][0][1] is the sha value for the current working
2167
entry1 = state._get_entry(0, path_utf8='file1')
2168
self.assertEqual(entry1_sha, entry1[1][0][1])
2169
# The 'size' field must also be set.
2170
self.assertEqual(25, entry1[1][0][2])
2171
entry1_state = entry1[1][0]
2172
entry2 = state._get_entry(0, path_utf8='dir/file2')
2173
self.assertEqual(entry2_sha, entry2[1][0][1])
2174
self.assertEqual(29, entry2[1][0][2])
2175
entry2_state = entry2[1][0]
2176
# Now, make sure that we don't have to re-read the content. The
2177
# packed_stat should match exactly.
2178
self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
2179
self.assertEqual(entry2_sha,
2180
target.get_file_sha1('file2-id', 'dir/file2'))
2181
self.assertEqual(entry1_state, entry1[1][0])
2182
self.assertEqual(entry2_state, entry2[1][0])
2185
class TestCommitTransform(tests.TestCaseWithTransport):
2187
def get_branch(self):
2188
tree = self.make_branch_and_tree('tree')
2190
self.addCleanup(tree.unlock)
2191
tree.commit('empty commit')
2194
def get_branch_and_transform(self):
2195
branch = self.get_branch()
2196
tt = TransformPreview(branch.basis_tree())
2197
self.addCleanup(tt.finalize)
2200
def test_commit_wrong_basis(self):
2201
branch = self.get_branch()
2202
basis = branch.repository.revision_tree(
2203
_mod_revision.NULL_REVISION)
2204
tt = TransformPreview(basis)
2205
self.addCleanup(tt.finalize)
2206
e = self.assertRaises(ValueError, tt.commit, branch, '')
2207
self.assertEqual('TreeTransform not based on branch basis: null:',
2210
def test_empy_commit(self):
2211
branch, tt = self.get_branch_and_transform()
2212
rev = tt.commit(branch, 'my message')
2213
self.assertEqual(2, branch.revno())
2214
repo = branch.repository
2215
self.assertEqual('my message', repo.get_revision(rev).message)
2217
def test_merge_parents(self):
2218
branch, tt = self.get_branch_and_transform()
2219
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2220
self.assertEqual(['rev1b', 'rev1c'],
2221
branch.basis_tree().get_parent_ids()[1:])
2223
def test_first_commit(self):
2224
branch = self.make_branch('branch')
2226
self.addCleanup(branch.unlock)
2227
tt = TransformPreview(branch.basis_tree())
2228
self.addCleanup(tt.finalize)
2229
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2230
rev = tt.commit(branch, 'my message')
2231
self.assertEqual([], branch.basis_tree().get_parent_ids())
2232
self.assertNotEqual(_mod_revision.NULL_REVISION,
2233
branch.last_revision())
2235
def test_first_commit_with_merge_parents(self):
2236
branch = self.make_branch('branch')
2238
self.addCleanup(branch.unlock)
2239
tt = TransformPreview(branch.basis_tree())
2240
self.addCleanup(tt.finalize)
2241
e = self.assertRaises(ValueError, tt.commit, branch,
2242
'my message', ['rev1b-id'])
2243
self.assertEqual('Cannot supply merge parents for first commit.',
2245
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2247
def test_add_files(self):
2248
branch, tt = self.get_branch_and_transform()
2249
tt.new_file('file', tt.root, 'contents', 'file-id')
2250
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2251
if SymlinkFeature.available():
2252
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2253
rev = tt.commit(branch, 'message')
2254
tree = branch.basis_tree()
2255
self.assertEqual('file', tree.id2path('file-id'))
2256
self.assertEqual('contents', tree.get_file_text('file-id'))
2257
self.assertEqual('dir', tree.id2path('dir-id'))
2258
if SymlinkFeature.available():
2259
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2260
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2262
def test_add_unversioned(self):
2263
branch, tt = self.get_branch_and_transform()
2264
tt.new_file('file', tt.root, 'contents')
2265
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2266
'message', strict=True)
2268
def test_modify_strict(self):
2269
branch, tt = self.get_branch_and_transform()
2270
tt.new_file('file', tt.root, 'contents', 'file-id')
2271
tt.commit(branch, 'message', strict=True)
2272
tt = TransformPreview(branch.basis_tree())
2273
self.addCleanup(tt.finalize)
2274
trans_id = tt.trans_id_file_id('file-id')
2275
tt.delete_contents(trans_id)
2276
tt.create_file('contents', trans_id)
2277
tt.commit(branch, 'message', strict=True)
2279
def test_commit_malformed(self):
2280
"""Committing a malformed transform should raise an exception.
2282
In this case, we are adding a file without adding its parent.
2284
branch, tt = self.get_branch_and_transform()
2285
parent_id = tt.trans_id_file_id('parent-id')
2286
tt.new_file('file', parent_id, 'contents', 'file-id')
2287
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2290
def test_commit_rich_revision_data(self):
2291
branch, tt = self.get_branch_and_transform()
2292
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2293
committer='me <me@example.com>',
2294
revprops={'foo': 'bar'}, revision_id='revid-1',
2295
authors=['Author1 <author1@example.com>',
2296
'Author2 <author2@example.com>',
2298
self.assertEqual('revid-1', rev_id)
2299
revision = branch.repository.get_revision(rev_id)
2300
self.assertEqual(1, revision.timestamp)
2301
self.assertEqual(43201, revision.timezone)
2302
self.assertEqual('me <me@example.com>', revision.committer)
2303
self.assertEqual(['Author1 <author1@example.com>',
2304
'Author2 <author2@example.com>'],
2305
revision.get_apparent_authors())
2306
del revision.properties['authors']
2307
self.assertEqual({'foo': 'bar',
2308
'branch-nick': 'tree'},
2309
revision.properties)
2311
def test_no_explicit_revprops(self):
2312
branch, tt = self.get_branch_and_transform()
2313
rev_id = tt.commit(branch, 'message', authors=[
2314
'Author1 <author1@example.com>',
2315
'Author2 <author2@example.com>', ])
2316
revision = branch.repository.get_revision(rev_id)
2317
self.assertEqual(['Author1 <author1@example.com>',
2318
'Author2 <author2@example.com>'],
2319
revision.get_apparent_authors())
2320
self.assertEqual('tree', revision.properties['branch-nick'])
2323
class TestBackupName(tests.TestCase):
2325
def test_deprecations(self):
2326
class MockTransform(object):
2328
def has_named_child(self, by_parent, parent_id, name):
2329
return name in by_parent.get(parent_id, [])
2331
class MockEntry(object):
2334
object.__init__(self)
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):
2337
724
tt = MockTransform()
2338
name1 = self.applyDeprecated(
2339
symbol_versioning.deprecated_in((2, 3, 0)),
2340
transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
2341
self.assertEqual('name.~1~', name1)
2342
name2 = self.applyDeprecated(
2343
symbol_versioning.deprecated_in((2, 3, 0)),
2344
transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
2345
self.assertEqual('name.~2~', name2)
2348
class TestFileMover(tests.TestCaseWithTransport):
2350
def test_file_mover(self):
2351
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2352
mover = _FileMover()
2353
mover.rename('a', 'q')
2354
self.assertPathExists('q')
2355
self.assertPathDoesNotExist('a')
2356
self.assertPathExists('q/b')
2357
self.assertPathExists('c')
2358
self.assertPathExists('c/d')
2360
def test_pre_delete_rollback(self):
2361
self.build_tree(['a/'])
2362
mover = _FileMover()
2363
mover.pre_delete('a', 'q')
2364
self.assertPathExists('q')
2365
self.assertPathDoesNotExist('a')
2367
self.assertPathDoesNotExist('q')
2368
self.assertPathExists('a')
2370
def test_apply_deletions(self):
2371
self.build_tree(['a/', 'b/'])
2372
mover = _FileMover()
2373
mover.pre_delete('a', 'q')
2374
mover.pre_delete('b', 'r')
2375
self.assertPathExists('q')
2376
self.assertPathExists('r')
2377
self.assertPathDoesNotExist('a')
2378
self.assertPathDoesNotExist('b')
2379
mover.apply_deletions()
2380
self.assertPathDoesNotExist('q')
2381
self.assertPathDoesNotExist('r')
2382
self.assertPathDoesNotExist('a')
2383
self.assertPathDoesNotExist('b')
2385
def test_file_mover_rollback(self):
2386
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2387
mover = _FileMover()
2388
mover.rename('c/d', 'c/f')
2389
mover.rename('c/e', 'c/d')
2391
mover.rename('a', 'c')
2392
except errors.FileExists, e:
2394
self.assertPathExists('a')
2395
self.assertPathExists('c/d')
2398
class Bogus(Exception):
2402
class TestTransformRollback(tests.TestCaseWithTransport):
2404
class ExceptionFileMover(_FileMover):
2406
def __init__(self, bad_source=None, bad_target=None):
2407
_FileMover.__init__(self)
2408
self.bad_source = bad_source
2409
self.bad_target = bad_target
2411
def rename(self, source, target):
2412
if (self.bad_source is not None and
2413
source.endswith(self.bad_source)):
2415
elif (self.bad_target is not None and
2416
target.endswith(self.bad_target)):
2419
_FileMover.rename(self, source, target)
2421
def test_rollback_rename(self):
2422
tree = self.make_branch_and_tree('.')
2423
self.build_tree(['a/', 'a/b'])
2424
tt = TreeTransform(tree)
2425
self.addCleanup(tt.finalize)
2426
a_id = tt.trans_id_tree_path('a')
2427
tt.adjust_path('c', tt.root, a_id)
2428
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2429
self.assertRaises(Bogus, tt.apply,
2430
_mover=self.ExceptionFileMover(bad_source='a'))
2431
self.assertPathExists('a')
2432
self.assertPathExists('a/b')
2434
self.assertPathExists('c')
2435
self.assertPathExists('c/d')
2437
def test_rollback_rename_into_place(self):
2438
tree = self.make_branch_and_tree('.')
2439
self.build_tree(['a/', 'a/b'])
2440
tt = TreeTransform(tree)
2441
self.addCleanup(tt.finalize)
2442
a_id = tt.trans_id_tree_path('a')
2443
tt.adjust_path('c', tt.root, a_id)
2444
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2445
self.assertRaises(Bogus, tt.apply,
2446
_mover=self.ExceptionFileMover(bad_target='c/d'))
2447
self.assertPathExists('a')
2448
self.assertPathExists('a/b')
2450
self.assertPathExists('c')
2451
self.assertPathExists('c/d')
2453
def test_rollback_deletion(self):
2454
tree = self.make_branch_and_tree('.')
2455
self.build_tree(['a/', 'a/b'])
2456
tt = TreeTransform(tree)
2457
self.addCleanup(tt.finalize)
2458
a_id = tt.trans_id_tree_path('a')
2459
tt.delete_contents(a_id)
2460
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2461
self.assertRaises(Bogus, tt.apply,
2462
_mover=self.ExceptionFileMover(bad_target='d'))
2463
self.assertPathExists('a')
2464
self.assertPathExists('a/b')
2467
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2468
"""Ensure treetransform creation errors can be safely cleaned up after"""
2470
def _override_globals_in_method(self, instance, method_name, globals):
2471
"""Replace method on instance with one with updated globals"""
2473
func = getattr(instance, method_name).im_func
2474
new_globals = dict(func.func_globals)
2475
new_globals.update(globals)
2476
new_func = types.FunctionType(func.func_code, new_globals,
2477
func.func_name, func.func_defaults)
2478
setattr(instance, method_name,
2479
types.MethodType(new_func, instance, instance.__class__))
2480
self.addCleanup(delattr, instance, method_name)
2483
def _fake_open_raises_before(name, mode):
2484
"""Like open() but raises before doing anything"""
2488
def _fake_open_raises_after(name, mode):
2489
"""Like open() but raises after creating file without returning"""
2490
open(name, mode).close()
2493
def create_transform_and_root_trans_id(self):
2494
"""Setup a transform creating a file in limbo"""
2495
tree = self.make_branch_and_tree('.')
2496
tt = TreeTransform(tree)
2497
return tt, tt.create_path("a", tt.root)
2499
def create_transform_and_subdir_trans_id(self):
2500
"""Setup a transform creating a directory containing a file in limbo"""
2501
tree = self.make_branch_and_tree('.')
2502
tt = TreeTransform(tree)
2503
d_trans_id = tt.create_path("d", tt.root)
2504
tt.create_directory(d_trans_id)
2505
f_trans_id = tt.create_path("a", d_trans_id)
2506
tt.adjust_path("a", d_trans_id, f_trans_id)
2507
return tt, f_trans_id
2509
def test_root_create_file_open_raises_before_creation(self):
2510
tt, trans_id = self.create_transform_and_root_trans_id()
2511
self._override_globals_in_method(tt, "create_file",
2512
{"open": self._fake_open_raises_before})
2513
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2514
path = tt._limbo_name(trans_id)
2515
self.assertPathDoesNotExist(path)
2517
self.assertPathDoesNotExist(tt._limbodir)
2519
def test_root_create_file_open_raises_after_creation(self):
2520
tt, trans_id = self.create_transform_and_root_trans_id()
2521
self._override_globals_in_method(tt, "create_file",
2522
{"open": self._fake_open_raises_after})
2523
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2524
path = tt._limbo_name(trans_id)
2525
self.assertPathExists(path)
2527
self.assertPathDoesNotExist(path)
2528
self.assertPathDoesNotExist(tt._limbodir)
2530
def test_subdir_create_file_open_raises_before_creation(self):
2531
tt, trans_id = self.create_transform_and_subdir_trans_id()
2532
self._override_globals_in_method(tt, "create_file",
2533
{"open": self._fake_open_raises_before})
2534
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2535
path = tt._limbo_name(trans_id)
2536
self.assertPathDoesNotExist(path)
2538
self.assertPathDoesNotExist(tt._limbodir)
2540
def test_subdir_create_file_open_raises_after_creation(self):
2541
tt, trans_id = self.create_transform_and_subdir_trans_id()
2542
self._override_globals_in_method(tt, "create_file",
2543
{"open": self._fake_open_raises_after})
2544
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2545
path = tt._limbo_name(trans_id)
2546
self.assertPathExists(path)
2548
self.assertPathDoesNotExist(path)
2549
self.assertPathDoesNotExist(tt._limbodir)
2552
class TestTransformMissingParent(tests.TestCaseWithTransport):
2554
def make_tt_with_versioned_dir(self):
2555
wt = self.make_branch_and_tree('.')
2556
self.build_tree(['dir/',])
2557
wt.add(['dir'], ['dir-id'])
2558
wt.commit('Create dir')
2559
tt = TreeTransform(wt)
2560
self.addCleanup(tt.finalize)
2563
def test_resolve_create_parent_for_versioned_file(self):
2564
wt, tt = self.make_tt_with_versioned_dir()
2565
dir_tid = tt.trans_id_tree_file_id('dir-id')
2566
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2567
tt.delete_contents(dir_tid)
2568
tt.unversion_file(dir_tid)
2569
conflicts = resolve_conflicts(tt)
2570
# one conflict for the missing directory, one for the unversioned
2572
self.assertLength(2, conflicts)
2574
def test_non_versioned_file_create_conflict(self):
2575
wt, tt = self.make_tt_with_versioned_dir()
2576
dir_tid = tt.trans_id_tree_file_id('dir-id')
2577
tt.new_file('file', dir_tid, 'Contents')
2578
tt.delete_contents(dir_tid)
2579
tt.unversion_file(dir_tid)
2580
conflicts = resolve_conflicts(tt)
2581
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2582
self.assertLength(1, conflicts)
2583
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2587
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2588
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2590
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2591
('', ''), ('directory', 'directory'), (False, False))
2594
class TestTransformPreview(tests.TestCaseWithTransport):
2596
def create_tree(self):
2597
tree = self.make_branch_and_tree('.')
2598
self.build_tree_contents([('a', 'content 1')])
2599
tree.set_root_id('TREE_ROOT')
2600
tree.add('a', 'a-id')
2601
tree.commit('rev1', rev_id='rev1')
2602
return tree.branch.repository.revision_tree('rev1')
2604
def get_empty_preview(self):
2605
repository = self.make_repository('repo')
2606
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2607
preview = TransformPreview(tree)
2608
self.addCleanup(preview.finalize)
2611
def test_transform_preview(self):
2612
revision_tree = self.create_tree()
2613
preview = TransformPreview(revision_tree)
2614
self.addCleanup(preview.finalize)
2616
def test_transform_preview_tree(self):
2617
revision_tree = self.create_tree()
2618
preview = TransformPreview(revision_tree)
2619
self.addCleanup(preview.finalize)
2620
preview.get_preview_tree()
2622
def test_transform_new_file(self):
2623
revision_tree = self.create_tree()
2624
preview = TransformPreview(revision_tree)
2625
self.addCleanup(preview.finalize)
2626
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2627
preview_tree = preview.get_preview_tree()
2628
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2630
preview_tree.get_file('file2-id').read(), 'content B\n')
2632
def test_diff_preview_tree(self):
2633
revision_tree = self.create_tree()
2634
preview = TransformPreview(revision_tree)
2635
self.addCleanup(preview.finalize)
2636
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2637
preview_tree = preview.get_preview_tree()
2639
show_diff_trees(revision_tree, preview_tree, out)
2640
lines = out.getvalue().splitlines()
2641
self.assertEqual(lines[0], "=== added file 'file2'")
2642
# 3 lines of diff administrivia
2643
self.assertEqual(lines[4], "+content B")
2645
def test_transform_conflicts(self):
2646
revision_tree = self.create_tree()
2647
preview = TransformPreview(revision_tree)
2648
self.addCleanup(preview.finalize)
2649
preview.new_file('a', preview.root, 'content 2')
2650
resolve_conflicts(preview)
2651
trans_id = preview.trans_id_file_id('a-id')
2652
self.assertEqual('a.moved', preview.final_name(trans_id))
2654
def get_tree_and_preview_tree(self):
2655
revision_tree = self.create_tree()
2656
preview = TransformPreview(revision_tree)
2657
self.addCleanup(preview.finalize)
2658
a_trans_id = preview.trans_id_file_id('a-id')
2659
preview.delete_contents(a_trans_id)
2660
preview.create_file('b content', a_trans_id)
2661
preview_tree = preview.get_preview_tree()
2662
return revision_tree, preview_tree
2664
def test_iter_changes(self):
2665
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2666
root = revision_tree.inventory.root.file_id
2667
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2668
(root, root), ('a', 'a'), ('file', 'file'),
2670
list(preview_tree.iter_changes(revision_tree)))
2672
def test_include_unchanged_succeeds(self):
2673
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2674
changes = preview_tree.iter_changes(revision_tree,
2675
include_unchanged=True)
2676
root = revision_tree.inventory.root.file_id
2678
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2680
def test_specific_files(self):
2681
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2682
changes = preview_tree.iter_changes(revision_tree,
2683
specific_files=[''])
2684
self.assertEqual([A_ENTRY], list(changes))
2686
def test_want_unversioned(self):
2687
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2688
changes = preview_tree.iter_changes(revision_tree,
2689
want_unversioned=True)
2690
self.assertEqual([A_ENTRY], list(changes))
2692
def test_ignore_extra_trees_no_specific_files(self):
2693
# extra_trees is harmless without specific_files, so we'll silently
2694
# accept it, even though we won't use it.
2695
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2696
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2698
def test_ignore_require_versioned_no_specific_files(self):
2699
# require_versioned is meaningless without specific_files.
2700
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2701
preview_tree.iter_changes(revision_tree, require_versioned=False)
2703
def test_ignore_pb(self):
2704
# pb could be supported, but TT.iter_changes doesn't support it.
2705
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2706
preview_tree.iter_changes(revision_tree)
2708
def test_kind(self):
2709
revision_tree = self.create_tree()
2710
preview = TransformPreview(revision_tree)
2711
self.addCleanup(preview.finalize)
2712
preview.new_file('file', preview.root, 'contents', 'file-id')
2713
preview.new_directory('directory', preview.root, 'dir-id')
2714
preview_tree = preview.get_preview_tree()
2715
self.assertEqual('file', preview_tree.kind('file-id'))
2716
self.assertEqual('directory', preview_tree.kind('dir-id'))
2718
def test_get_file_mtime(self):
2719
preview = self.get_empty_preview()
2720
file_trans_id = preview.new_file('file', preview.root, 'contents',
2722
limbo_path = preview._limbo_name(file_trans_id)
2723
preview_tree = preview.get_preview_tree()
2724
self.assertEqual(os.stat(limbo_path).st_mtime,
2725
preview_tree.get_file_mtime('file-id'))
2727
def test_get_file_mtime_renamed(self):
2728
work_tree = self.make_branch_and_tree('tree')
2729
self.build_tree(['tree/file'])
2730
work_tree.add('file', 'file-id')
2731
preview = TransformPreview(work_tree)
2732
self.addCleanup(preview.finalize)
2733
file_trans_id = preview.trans_id_tree_file_id('file-id')
2734
preview.adjust_path('renamed', preview.root, file_trans_id)
2735
preview_tree = preview.get_preview_tree()
2736
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2737
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2739
def test_get_file_size(self):
2740
work_tree = self.make_branch_and_tree('tree')
2741
self.build_tree_contents([('tree/old', 'old')])
2742
work_tree.add('old', 'old-id')
2743
preview = TransformPreview(work_tree)
2744
self.addCleanup(preview.finalize)
2745
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2747
tree = preview.get_preview_tree()
2748
self.assertEqual(len('old'), tree.get_file_size('old-id'))
2749
self.assertEqual(len('contents'), tree.get_file_size('new-id'))
2751
def test_get_file(self):
2752
preview = self.get_empty_preview()
2753
preview.new_file('file', preview.root, 'contents', 'file-id')
2754
preview_tree = preview.get_preview_tree()
2755
tree_file = preview_tree.get_file('file-id')
2757
self.assertEqual('contents', tree_file.read())
2761
def test_get_symlink_target(self):
2762
self.requireFeature(SymlinkFeature)
2763
preview = self.get_empty_preview()
2764
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2765
preview_tree = preview.get_preview_tree()
2766
self.assertEqual('target',
2767
preview_tree.get_symlink_target('symlink-id'))
2769
def test_all_file_ids(self):
2770
tree = self.make_branch_and_tree('tree')
2771
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2772
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2773
preview = TransformPreview(tree)
2774
self.addCleanup(preview.finalize)
2775
preview.unversion_file(preview.trans_id_file_id('b-id'))
2776
c_trans_id = preview.trans_id_file_id('c-id')
2777
preview.unversion_file(c_trans_id)
2778
preview.version_file('c-id', c_trans_id)
2779
preview_tree = preview.get_preview_tree()
2780
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2781
preview_tree.all_file_ids())
2783
def test_path2id_deleted_unchanged(self):
2784
tree = self.make_branch_and_tree('tree')
2785
self.build_tree(['tree/unchanged', 'tree/deleted'])
2786
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2787
preview = TransformPreview(tree)
2788
self.addCleanup(preview.finalize)
2789
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2790
preview_tree = preview.get_preview_tree()
2791
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2792
self.assertIs(None, preview_tree.path2id('deleted'))
2794
def test_path2id_created(self):
2795
tree = self.make_branch_and_tree('tree')
2796
self.build_tree(['tree/unchanged'])
2797
tree.add(['unchanged'], ['unchanged-id'])
2798
preview = TransformPreview(tree)
2799
self.addCleanup(preview.finalize)
2800
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2801
'contents', 'new-id')
2802
preview_tree = preview.get_preview_tree()
2803
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2805
def test_path2id_moved(self):
2806
tree = self.make_branch_and_tree('tree')
2807
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2808
tree.add(['old_parent', 'old_parent/child'],
2809
['old_parent-id', 'child-id'])
2810
preview = TransformPreview(tree)
2811
self.addCleanup(preview.finalize)
2812
new_parent = preview.new_directory('new_parent', preview.root,
2814
preview.adjust_path('child', new_parent,
2815
preview.trans_id_file_id('child-id'))
2816
preview_tree = preview.get_preview_tree()
2817
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2818
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2820
def test_path2id_renamed_parent(self):
2821
tree = self.make_branch_and_tree('tree')
2822
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2823
tree.add(['old_name', 'old_name/child'],
2824
['parent-id', 'child-id'])
2825
preview = TransformPreview(tree)
2826
self.addCleanup(preview.finalize)
2827
preview.adjust_path('new_name', preview.root,
2828
preview.trans_id_file_id('parent-id'))
2829
preview_tree = preview.get_preview_tree()
2830
self.assertIs(None, preview_tree.path2id('old_name/child'))
2831
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2833
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2834
preview_tree = tt.get_preview_tree()
2835
preview_result = list(preview_tree.iter_entries_by_dir(
2839
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2840
self.assertEqual(actual_result, preview_result)
2842
def test_iter_entries_by_dir_new(self):
2843
tree = self.make_branch_and_tree('tree')
2844
tt = TreeTransform(tree)
2845
tt.new_file('new', tt.root, 'contents', 'new-id')
2846
self.assertMatchingIterEntries(tt)
2848
def test_iter_entries_by_dir_deleted(self):
2849
tree = self.make_branch_and_tree('tree')
2850
self.build_tree(['tree/deleted'])
2851
tree.add('deleted', 'deleted-id')
2852
tt = TreeTransform(tree)
2853
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2854
self.assertMatchingIterEntries(tt)
2856
def test_iter_entries_by_dir_unversioned(self):
2857
tree = self.make_branch_and_tree('tree')
2858
self.build_tree(['tree/removed'])
2859
tree.add('removed', 'removed-id')
2860
tt = TreeTransform(tree)
2861
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2862
self.assertMatchingIterEntries(tt)
2864
def test_iter_entries_by_dir_moved(self):
2865
tree = self.make_branch_and_tree('tree')
2866
self.build_tree(['tree/moved', 'tree/new_parent/'])
2867
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2868
tt = TreeTransform(tree)
2869
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2870
tt.trans_id_file_id('moved-id'))
2871
self.assertMatchingIterEntries(tt)
2873
def test_iter_entries_by_dir_specific_file_ids(self):
2874
tree = self.make_branch_and_tree('tree')
2875
tree.set_root_id('tree-root-id')
2876
self.build_tree(['tree/parent/', 'tree/parent/child'])
2877
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2878
tt = TreeTransform(tree)
2879
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2881
def test_symlink_content_summary(self):
2882
self.requireFeature(SymlinkFeature)
2883
preview = self.get_empty_preview()
2884
preview.new_symlink('path', preview.root, 'target', 'path-id')
2885
summary = preview.get_preview_tree().path_content_summary('path')
2886
self.assertEqual(('symlink', None, None, 'target'), summary)
2888
def test_missing_content_summary(self):
2889
preview = self.get_empty_preview()
2890
summary = preview.get_preview_tree().path_content_summary('path')
2891
self.assertEqual(('missing', None, None, None), summary)
2893
def test_deleted_content_summary(self):
2894
tree = self.make_branch_and_tree('tree')
2895
self.build_tree(['tree/path/'])
2897
preview = TransformPreview(tree)
2898
self.addCleanup(preview.finalize)
2899
preview.delete_contents(preview.trans_id_tree_path('path'))
2900
summary = preview.get_preview_tree().path_content_summary('path')
2901
self.assertEqual(('missing', None, None, None), summary)
2903
def test_file_content_summary_executable(self):
2904
preview = self.get_empty_preview()
2905
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2906
preview.set_executability(True, path_id)
2907
summary = preview.get_preview_tree().path_content_summary('path')
2908
self.assertEqual(4, len(summary))
2909
self.assertEqual('file', summary[0])
2910
# size must be known
2911
self.assertEqual(len('contents'), summary[1])
2913
self.assertEqual(True, summary[2])
2914
# will not have hash (not cheap to determine)
2915
self.assertIs(None, summary[3])
2917
def test_change_executability(self):
2918
tree = self.make_branch_and_tree('tree')
2919
self.build_tree(['tree/path'])
2921
preview = TransformPreview(tree)
2922
self.addCleanup(preview.finalize)
2923
path_id = preview.trans_id_tree_path('path')
2924
preview.set_executability(True, path_id)
2925
summary = preview.get_preview_tree().path_content_summary('path')
2926
self.assertEqual(True, summary[2])
2928
def test_file_content_summary_non_exec(self):
2929
preview = self.get_empty_preview()
2930
preview.new_file('path', preview.root, 'contents', 'path-id')
2931
summary = preview.get_preview_tree().path_content_summary('path')
2932
self.assertEqual(4, len(summary))
2933
self.assertEqual('file', summary[0])
2934
# size must be known
2935
self.assertEqual(len('contents'), summary[1])
2937
self.assertEqual(False, summary[2])
2938
# will not have hash (not cheap to determine)
2939
self.assertIs(None, summary[3])
2941
def test_dir_content_summary(self):
2942
preview = self.get_empty_preview()
2943
preview.new_directory('path', preview.root, 'path-id')
2944
summary = preview.get_preview_tree().path_content_summary('path')
2945
self.assertEqual(('directory', None, None, None), summary)
2947
def test_tree_content_summary(self):
2948
preview = self.get_empty_preview()
2949
path = preview.new_directory('path', preview.root, 'path-id')
2950
preview.set_tree_reference('rev-1', path)
2951
summary = preview.get_preview_tree().path_content_summary('path')
2952
self.assertEqual(4, len(summary))
2953
self.assertEqual('tree-reference', summary[0])
2955
def test_annotate(self):
2956
tree = self.make_branch_and_tree('tree')
2957
self.build_tree_contents([('tree/file', 'a\n')])
2958
tree.add('file', 'file-id')
2959
tree.commit('a', rev_id='one')
2960
self.build_tree_contents([('tree/file', 'a\nb\n')])
2961
preview = TransformPreview(tree)
2962
self.addCleanup(preview.finalize)
2963
file_trans_id = preview.trans_id_file_id('file-id')
2964
preview.delete_contents(file_trans_id)
2965
preview.create_file('a\nb\nc\n', file_trans_id)
2966
preview_tree = preview.get_preview_tree()
2972
annotation = preview_tree.annotate_iter('file-id', 'me:')
2973
self.assertEqual(expected, annotation)
2975
def test_annotate_missing(self):
2976
preview = self.get_empty_preview()
2977
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2978
preview_tree = preview.get_preview_tree()
2984
annotation = preview_tree.annotate_iter('file-id', 'me:')
2985
self.assertEqual(expected, annotation)
2987
def test_annotate_rename(self):
2988
tree = self.make_branch_and_tree('tree')
2989
self.build_tree_contents([('tree/file', 'a\n')])
2990
tree.add('file', 'file-id')
2991
tree.commit('a', rev_id='one')
2992
preview = TransformPreview(tree)
2993
self.addCleanup(preview.finalize)
2994
file_trans_id = preview.trans_id_file_id('file-id')
2995
preview.adjust_path('newname', preview.root, file_trans_id)
2996
preview_tree = preview.get_preview_tree()
3000
annotation = preview_tree.annotate_iter('file-id', 'me:')
3001
self.assertEqual(expected, annotation)
3003
def test_annotate_deleted(self):
3004
tree = self.make_branch_and_tree('tree')
3005
self.build_tree_contents([('tree/file', 'a\n')])
3006
tree.add('file', 'file-id')
3007
tree.commit('a', rev_id='one')
3008
self.build_tree_contents([('tree/file', 'a\nb\n')])
3009
preview = TransformPreview(tree)
3010
self.addCleanup(preview.finalize)
3011
file_trans_id = preview.trans_id_file_id('file-id')
3012
preview.delete_contents(file_trans_id)
3013
preview_tree = preview.get_preview_tree()
3014
annotation = preview_tree.annotate_iter('file-id', 'me:')
3015
self.assertIs(None, annotation)
3017
def test_stored_kind(self):
3018
preview = self.get_empty_preview()
3019
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3020
preview_tree = preview.get_preview_tree()
3021
self.assertEqual('file', preview_tree.stored_kind('file-id'))
3023
def test_is_executable(self):
3024
preview = self.get_empty_preview()
3025
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3026
preview.set_executability(True, preview.trans_id_file_id('file-id'))
3027
preview_tree = preview.get_preview_tree()
3028
self.assertEqual(True, preview_tree.is_executable('file-id'))
3030
def test_get_set_parent_ids(self):
3031
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3032
self.assertEqual([], preview_tree.get_parent_ids())
3033
preview_tree.set_parent_ids(['rev-1'])
3034
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
3036
def test_plan_file_merge(self):
3037
work_a = self.make_branch_and_tree('wta')
3038
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3039
work_a.add('file', 'file-id')
3040
base_id = work_a.commit('base version')
3041
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3042
preview = TransformPreview(work_a)
3043
self.addCleanup(preview.finalize)
3044
trans_id = preview.trans_id_file_id('file-id')
3045
preview.delete_contents(trans_id)
3046
preview.create_file('b\nc\nd\ne\n', trans_id)
3047
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3048
tree_a = preview.get_preview_tree()
3049
tree_a.set_parent_ids([base_id])
3051
('killed-a', 'a\n'),
3052
('killed-b', 'b\n'),
3053
('unchanged', 'c\n'),
3054
('unchanged', 'd\n'),
3057
], list(tree_a.plan_file_merge('file-id', tree_b)))
3059
def test_plan_file_merge_revision_tree(self):
3060
work_a = self.make_branch_and_tree('wta')
3061
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3062
work_a.add('file', 'file-id')
3063
base_id = work_a.commit('base version')
3064
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3065
preview = TransformPreview(work_a.basis_tree())
3066
self.addCleanup(preview.finalize)
3067
trans_id = preview.trans_id_file_id('file-id')
3068
preview.delete_contents(trans_id)
3069
preview.create_file('b\nc\nd\ne\n', trans_id)
3070
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3071
tree_a = preview.get_preview_tree()
3072
tree_a.set_parent_ids([base_id])
3074
('killed-a', 'a\n'),
3075
('killed-b', 'b\n'),
3076
('unchanged', 'c\n'),
3077
('unchanged', 'd\n'),
3080
], list(tree_a.plan_file_merge('file-id', tree_b)))
3082
def test_walkdirs(self):
3083
preview = self.get_empty_preview()
3084
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3085
# FIXME: new_directory should mark root.
3086
preview.fixup_new_roots()
3087
preview_tree = preview.get_preview_tree()
3088
file_trans_id = preview.new_file('a', preview.root, 'contents',
3090
expected = [(('', 'tree-root'),
3091
[('a', 'a', 'file', None, 'a-id', 'file')])]
3092
self.assertEqual(expected, list(preview_tree.walkdirs()))
3094
def test_extras(self):
3095
work_tree = self.make_branch_and_tree('tree')
3096
self.build_tree(['tree/removed-file', 'tree/existing-file',
3097
'tree/not-removed-file'])
3098
work_tree.add(['removed-file', 'not-removed-file'])
3099
preview = TransformPreview(work_tree)
3100
self.addCleanup(preview.finalize)
3101
preview.new_file('new-file', preview.root, 'contents')
3102
preview.new_file('new-versioned-file', preview.root, 'contents',
3104
tree = preview.get_preview_tree()
3105
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3106
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
3109
def test_merge_into_preview(self):
3110
work_tree = self.make_branch_and_tree('tree')
3111
self.build_tree_contents([('tree/file','b\n')])
3112
work_tree.add('file', 'file-id')
3113
work_tree.commit('first commit')
3114
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
3115
self.build_tree_contents([('child/file','b\nc\n')])
3116
child_tree.commit('child commit')
3117
child_tree.lock_write()
3118
self.addCleanup(child_tree.unlock)
3119
work_tree.lock_write()
3120
self.addCleanup(work_tree.unlock)
3121
preview = TransformPreview(work_tree)
3122
self.addCleanup(preview.finalize)
3123
file_trans_id = preview.trans_id_file_id('file-id')
3124
preview.delete_contents(file_trans_id)
3125
preview.create_file('a\nb\n', file_trans_id)
3126
preview_tree = preview.get_preview_tree()
3127
merger = Merger.from_revision_ids(None, preview_tree,
3128
child_tree.branch.last_revision(),
3129
other_branch=child_tree.branch,
3130
tree_branch=work_tree.branch)
3131
merger.merge_type = Merge3Merger
3132
tt = merger.make_merger().make_preview_transform()
3133
self.addCleanup(tt.finalize)
3134
final_tree = tt.get_preview_tree()
3135
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3137
def test_merge_preview_into_workingtree(self):
3138
tree = self.make_branch_and_tree('tree')
3139
tree.set_root_id('TREE_ROOT')
3140
tt = TransformPreview(tree)
3141
self.addCleanup(tt.finalize)
3142
tt.new_file('name', tt.root, 'content', 'file-id')
3143
tree2 = self.make_branch_and_tree('tree2')
3144
tree2.set_root_id('TREE_ROOT')
3145
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3146
None, tree.basis_tree())
3147
merger.merge_type = Merge3Merger
3150
def test_merge_preview_into_workingtree_handles_conflicts(self):
3151
tree = self.make_branch_and_tree('tree')
3152
self.build_tree_contents([('tree/foo', 'bar')])
3153
tree.add('foo', 'foo-id')
3155
tt = TransformPreview(tree)
3156
self.addCleanup(tt.finalize)
3157
trans_id = tt.trans_id_file_id('foo-id')
3158
tt.delete_contents(trans_id)
3159
tt.create_file('baz', trans_id)
3160
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
3161
self.build_tree_contents([('tree2/foo', 'qux')])
3163
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3164
pb, tree.basis_tree())
3165
merger.merge_type = Merge3Merger
3168
def test_has_filename(self):
3169
wt = self.make_branch_and_tree('tree')
3170
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3171
tt = TransformPreview(wt)
3172
removed_id = tt.trans_id_tree_path('removed')
3173
tt.delete_contents(removed_id)
3174
tt.new_file('new', tt.root, 'contents')
3175
modified_id = tt.trans_id_tree_path('modified')
3176
tt.delete_contents(modified_id)
3177
tt.create_file('modified-contents', modified_id)
3178
self.addCleanup(tt.finalize)
3179
tree = tt.get_preview_tree()
3180
self.assertTrue(tree.has_filename('unmodified'))
3181
self.assertFalse(tree.has_filename('not-present'))
3182
self.assertFalse(tree.has_filename('removed'))
3183
self.assertTrue(tree.has_filename('new'))
3184
self.assertTrue(tree.has_filename('modified'))
3186
def test_is_executable(self):
3187
tree = self.make_branch_and_tree('tree')
3188
preview = TransformPreview(tree)
3189
self.addCleanup(preview.finalize)
3190
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3191
preview_tree = preview.get_preview_tree()
3192
self.assertEqual(False, preview_tree.is_executable('baz-id',
3194
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3196
def test_commit_preview_tree(self):
3197
tree = self.make_branch_and_tree('tree')
3198
rev_id = tree.commit('rev1')
3199
tree.branch.lock_write()
3200
self.addCleanup(tree.branch.unlock)
3201
tt = TransformPreview(tree)
3202
tt.new_file('file', tt.root, 'contents', 'file_id')
3203
self.addCleanup(tt.finalize)
3204
preview = tt.get_preview_tree()
3205
preview.set_parent_ids([rev_id])
3206
builder = tree.branch.get_commit_builder([rev_id])
3207
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3208
builder.finish_inventory()
3209
rev2_id = builder.commit('rev2')
3210
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3211
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3213
def test_ascii_limbo_paths(self):
3214
self.requireFeature(tests.UnicodeFilenameFeature)
3215
branch = self.make_branch('any')
3216
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3217
tt = TransformPreview(tree)
3218
self.addCleanup(tt.finalize)
3219
foo_id = tt.new_directory('', ROOT_PARENT)
3220
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3221
limbo_path = tt._limbo_name(bar_id)
3222
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3225
class FakeSerializer(object):
3226
"""Serializer implementation that simply returns the input.
3228
The input is returned in the order used by pack.ContainerPushParser.
3231
def bytes_record(bytes, names):
3235
class TestSerializeTransform(tests.TestCaseWithTransport):
3237
_test_needs_features = [tests.UnicodeFilenameFeature]
3239
def get_preview(self, tree=None):
3241
tree = self.make_branch_and_tree('tree')
3242
tt = TransformPreview(tree)
3243
self.addCleanup(tt.finalize)
3246
def assertSerializesTo(self, expected, tt):
3247
records = list(tt.serialize(FakeSerializer()))
3248
self.assertEqual(expected, records)
3251
def default_attribs():
3256
'_new_executability': {},
3258
'_tree_path_ids': {'': 'new-0'},
3260
'_removed_contents': [],
3261
'_non_present_ids': {},
3264
def make_records(self, attribs, contents):
3266
(((('attribs'),),), bencode.bencode(attribs))]
3267
records.extend([(((n, k),), c) for n, k, c in contents])
3270
def creation_records(self):
3271
attribs = self.default_attribs()
3272
attribs['_id_number'] = 3
3273
attribs['_new_name'] = {
3274
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3275
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3276
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3277
attribs['_new_executability'] = {'new-1': 1}
3279
('new-1', 'file', 'i 1\nbar\n'),
3280
('new-2', 'directory', ''),
3282
return self.make_records(attribs, contents)
3284
def test_serialize_creation(self):
3285
tt = self.get_preview()
3286
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3287
tt.new_directory('qux', tt.root, 'quxx')
3288
self.assertSerializesTo(self.creation_records(), tt)
3290
def test_deserialize_creation(self):
3291
tt = self.get_preview()
3292
tt.deserialize(iter(self.creation_records()))
3293
self.assertEqual(3, tt._id_number)
3294
self.assertEqual({'new-1': u'foo\u1234',
3295
'new-2': 'qux'}, tt._new_name)
3296
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3297
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3298
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3299
self.assertEqual({'new-1': True}, tt._new_executability)
3300
self.assertEqual({'new-1': 'file',
3301
'new-2': 'directory'}, tt._new_contents)
3302
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3304
foo_content = foo_limbo.read()
3307
self.assertEqual('bar', foo_content)
3309
def symlink_creation_records(self):
3310
attribs = self.default_attribs()
3311
attribs['_id_number'] = 2
3312
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3313
attribs['_new_parent'] = {'new-1': 'new-0'}
3314
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3315
return self.make_records(attribs, contents)
3317
def test_serialize_symlink_creation(self):
3318
self.requireFeature(tests.SymlinkFeature)
3319
tt = self.get_preview()
3320
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3321
self.assertSerializesTo(self.symlink_creation_records(), tt)
3323
def test_deserialize_symlink_creation(self):
3324
self.requireFeature(tests.SymlinkFeature)
3325
tt = self.get_preview()
3326
tt.deserialize(iter(self.symlink_creation_records()))
3327
abspath = tt._limbo_name('new-1')
3328
foo_content = osutils.readlink(abspath)
3329
self.assertEqual(u'bar\u1234', foo_content)
3331
def make_destruction_preview(self):
3332
tree = self.make_branch_and_tree('.')
3333
self.build_tree([u'foo\u1234', 'bar'])
3334
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3335
return self.get_preview(tree)
3337
def destruction_records(self):
3338
attribs = self.default_attribs()
3339
attribs['_id_number'] = 3
3340
attribs['_removed_id'] = ['new-1']
3341
attribs['_removed_contents'] = ['new-2']
3342
attribs['_tree_path_ids'] = {
3344
u'foo\u1234'.encode('utf-8'): 'new-1',
3347
return self.make_records(attribs, [])
3349
def test_serialize_destruction(self):
3350
tt = self.make_destruction_preview()
3351
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3352
tt.unversion_file(foo_trans_id)
3353
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3354
tt.delete_contents(bar_trans_id)
3355
self.assertSerializesTo(self.destruction_records(), tt)
3357
def test_deserialize_destruction(self):
3358
tt = self.make_destruction_preview()
3359
tt.deserialize(iter(self.destruction_records()))
3360
self.assertEqual({u'foo\u1234': 'new-1',
3362
'': tt.root}, tt._tree_path_ids)
3363
self.assertEqual({'new-1': u'foo\u1234',
3365
tt.root: ''}, tt._tree_id_paths)
3366
self.assertEqual(set(['new-1']), tt._removed_id)
3367
self.assertEqual(set(['new-2']), tt._removed_contents)
3369
def missing_records(self):
3370
attribs = self.default_attribs()
3371
attribs['_id_number'] = 2
3372
attribs['_non_present_ids'] = {
3374
return self.make_records(attribs, [])
3376
def test_serialize_missing(self):
3377
tt = self.get_preview()
3378
boo_trans_id = tt.trans_id_file_id('boo')
3379
self.assertSerializesTo(self.missing_records(), tt)
3381
def test_deserialize_missing(self):
3382
tt = self.get_preview()
3383
tt.deserialize(iter(self.missing_records()))
3384
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3386
def make_modification_preview(self):
3387
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3388
LINES_TWO = 'z\nbb\nx\ndd\n'
3389
tree = self.make_branch_and_tree('tree')
3390
self.build_tree_contents([('tree/file', LINES_ONE)])
3391
tree.add('file', 'file-id')
3392
return self.get_preview(tree), LINES_TWO
3394
def modification_records(self):
3395
attribs = self.default_attribs()
3396
attribs['_id_number'] = 2
3397
attribs['_tree_path_ids'] = {
3400
attribs['_removed_contents'] = ['new-1']
3401
contents = [('new-1', 'file',
3402
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3403
return self.make_records(attribs, contents)
3405
def test_serialize_modification(self):
3406
tt, LINES = self.make_modification_preview()
3407
trans_id = tt.trans_id_file_id('file-id')
3408
tt.delete_contents(trans_id)
3409
tt.create_file(LINES, trans_id)
3410
self.assertSerializesTo(self.modification_records(), tt)
3412
def test_deserialize_modification(self):
3413
tt, LINES = self.make_modification_preview()
3414
tt.deserialize(iter(self.modification_records()))
3415
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3417
def make_kind_change_preview(self):
3418
LINES = 'a\nb\nc\nd\n'
3419
tree = self.make_branch_and_tree('tree')
3420
self.build_tree(['tree/foo/'])
3421
tree.add('foo', 'foo-id')
3422
return self.get_preview(tree), LINES
3424
def kind_change_records(self):
3425
attribs = self.default_attribs()
3426
attribs['_id_number'] = 2
3427
attribs['_tree_path_ids'] = {
3430
attribs['_removed_contents'] = ['new-1']
3431
contents = [('new-1', 'file',
3432
'i 4\na\nb\nc\nd\n\n')]
3433
return self.make_records(attribs, contents)
3435
def test_serialize_kind_change(self):
3436
tt, LINES = self.make_kind_change_preview()
3437
trans_id = tt.trans_id_file_id('foo-id')
3438
tt.delete_contents(trans_id)
3439
tt.create_file(LINES, trans_id)
3440
self.assertSerializesTo(self.kind_change_records(), tt)
3442
def test_deserialize_kind_change(self):
3443
tt, LINES = self.make_kind_change_preview()
3444
tt.deserialize(iter(self.kind_change_records()))
3445
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3447
def make_add_contents_preview(self):
3448
LINES = 'a\nb\nc\nd\n'
3449
tree = self.make_branch_and_tree('tree')
3450
self.build_tree(['tree/foo'])
3452
os.unlink('tree/foo')
3453
return self.get_preview(tree), LINES
3455
def add_contents_records(self):
3456
attribs = self.default_attribs()
3457
attribs['_id_number'] = 2
3458
attribs['_tree_path_ids'] = {
3461
contents = [('new-1', 'file',
3462
'i 4\na\nb\nc\nd\n\n')]
3463
return self.make_records(attribs, contents)
3465
def test_serialize_add_contents(self):
3466
tt, LINES = self.make_add_contents_preview()
3467
trans_id = tt.trans_id_tree_path('foo')
3468
tt.create_file(LINES, trans_id)
3469
self.assertSerializesTo(self.add_contents_records(), tt)
3471
def test_deserialize_add_contents(self):
3472
tt, LINES = self.make_add_contents_preview()
3473
tt.deserialize(iter(self.add_contents_records()))
3474
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3476
def test_get_parents_lines(self):
3477
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3478
LINES_TWO = 'z\nbb\nx\ndd\n'
3479
tree = self.make_branch_and_tree('tree')
3480
self.build_tree_contents([('tree/file', LINES_ONE)])
3481
tree.add('file', 'file-id')
3482
tt = self.get_preview(tree)
3483
trans_id = tt.trans_id_tree_path('file')
3484
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3485
tt._get_parents_lines(trans_id))
3487
def test_get_parents_texts(self):
3488
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3489
LINES_TWO = 'z\nbb\nx\ndd\n'
3490
tree = self.make_branch_and_tree('tree')
3491
self.build_tree_contents([('tree/file', LINES_ONE)])
3492
tree.add('file', 'file-id')
3493
tt = self.get_preview(tree)
3494
trans_id = tt.trans_id_tree_path('file')
3495
self.assertEqual((LINES_ONE,),
3496
tt._get_parents_texts(trans_id))
3499
class TestOrphan(tests.TestCaseWithTransport):
3501
def test_no_orphan_for_transform_preview(self):
3502
tree = self.make_branch_and_tree('tree')
3503
tt = transform.TransformPreview(tree)
3504
self.addCleanup(tt.finalize)
3505
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3507
def _set_orphan_policy(self, wt, policy):
3508
wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
3511
def _prepare_orphan(self, wt):
3512
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3513
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3514
wt.commit('add dir and file ignoring foo')
3515
tt = transform.TreeTransform(wt)
3516
self.addCleanup(tt.finalize)
3517
# dir and bar are deleted
3518
dir_tid = tt.trans_id_tree_path('dir')
3519
file_tid = tt.trans_id_tree_path('dir/file')
3520
orphan_tid = tt.trans_id_tree_path('dir/foo')
3521
tt.delete_contents(file_tid)
3522
tt.unversion_file(file_tid)
3523
tt.delete_contents(dir_tid)
3524
tt.unversion_file(dir_tid)
3525
# There should be a conflict because dir still contain foo
3526
raw_conflicts = tt.find_conflicts()
3527
self.assertLength(1, raw_conflicts)
3528
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3529
return tt, orphan_tid
3531
def test_new_orphan_created(self):
3532
wt = self.make_branch_and_tree('.')
3533
self._set_orphan_policy(wt, 'move')
3534
tt, orphan_tid = self._prepare_orphan(wt)
3537
warnings.append(args[0] % args[1:])
3538
self.overrideAttr(trace, 'warning', warning)
3539
remaining_conflicts = resolve_conflicts(tt)
3540
self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
3542
# Yeah for resolved conflicts !
3543
self.assertLength(0, remaining_conflicts)
3544
# We have a new orphan
3545
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3546
self.assertEquals('bzr-orphans',
3547
tt.final_name(tt.final_parent(orphan_tid)))
3549
def test_never_orphan(self):
3550
wt = self.make_branch_and_tree('.')
3551
self._set_orphan_policy(wt, 'conflict')
3552
tt, orphan_tid = self._prepare_orphan(wt)
3553
remaining_conflicts = resolve_conflicts(tt)
3554
self.assertLength(1, remaining_conflicts)
3555
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3556
remaining_conflicts.pop())
3558
def test_orphan_error(self):
3559
def bogus_orphan(tt, orphan_id, parent_id):
3560
raise transform.OrphaningError(tt.final_name(orphan_id),
3561
tt.final_name(parent_id))
3562
transform.orphaning_registry.register('bogus', bogus_orphan,
3563
'Raise an error when orphaning')
3564
wt = self.make_branch_and_tree('.')
3565
self._set_orphan_policy(wt, 'bogus')
3566
tt, orphan_tid = self._prepare_orphan(wt)
3567
remaining_conflicts = resolve_conflicts(tt)
3568
self.assertLength(1, remaining_conflicts)
3569
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3570
remaining_conflicts.pop())
3572
def test_unknown_orphan_policy(self):
3573
wt = self.make_branch_and_tree('.')
3574
# Set a fictional policy nobody ever implemented
3575
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3576
tt, orphan_tid = self._prepare_orphan(wt)
3579
warnings.append(args[0] % args[1:])
3580
self.overrideAttr(trace, 'warning', warning)
3581
remaining_conflicts = resolve_conflicts(tt)
3582
# We fallback to the default policy which create a conflict
3583
self.assertLength(1, remaining_conflicts)
3584
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3585
remaining_conflicts.pop())
3586
self.assertLength(1, warnings)
3587
self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')
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~')