479
940
rename.set_executability(True, myfile)
482
def test_find_interesting(self):
483
create, root = self.get_transform()
485
create.new_file('vfile', root, 'myfile-text', 'myfile-id')
486
create.new_file('uvfile', root, 'othertext')
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',
488
self.assertEqual(find_interesting(wt, wt, ['vfile']),
490
self.assertRaises(NotVersionedError, find_interesting, wt, wt,
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'))
494
1585
class TransformGroup(object):
495
def __init__(self, dirname):
1587
def __init__(self, dirname, root_id):
496
1588
self.name = dirname
497
1589
os.mkdir(dirname)
498
1590
self.wt = BzrDir.create_standalone_workingtree(dirname)
1591
self.wt.set_root_id(root_id)
499
1592
self.b = self.wt.branch
500
1593
self.tt = TreeTransform(self.wt)
501
1594
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
503
1597
def conflict_text(tree, merge):
504
1598
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
505
1599
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
508
1602
class TestTransformMerge(TestCaseInTempDir):
509
1604
def test_text_merge(self):
510
base = TransformGroup("base")
1605
root_id = generate_ids.gen_root_id()
1606
base = TransformGroup("base", root_id)
511
1607
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
512
1608
base.tt.new_file('b', base.root, 'b1', 'b')
513
1609
base.tt.new_file('c', base.root, 'c', 'c')
698
1798
a.add(['foo', 'foo/bar', 'foo/baz'])
699
1799
a.commit('initial commit')
700
1800
b = BzrDir.create_standalone_workingtree('b')
701
build_tree(a.basis_tree(), b)
1801
basis = a.basis_tree()
1803
self.addCleanup(basis.unlock)
1804
build_tree(basis, b)
702
1805
self.assertIs(os.path.isdir('b/foo'), True)
703
1806
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
704
1807
self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
706
class MockTransform(object):
708
def has_named_child(self, by_parent, parent_id, name):
709
for child_id in by_parent[parent_id]:
713
elif name == "name.~%s~" % child_id:
717
class MockEntry(object):
719
object.__init__(self)
722
class TestGetBackupName(TestCase):
723
def test_get_backup_name(self):
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)
724
2337
tt = MockTransform()
725
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
726
self.assertEqual(name, 'name.~1~')
727
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
728
self.assertEqual(name, 'name.~2~')
729
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
730
self.assertEqual(name, 'name.~1~')
731
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
732
self.assertEqual(name, 'name.~1~')
733
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
734
self.assertEqual(name, 'name.~4~')
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)
2551
def test_rename_in_limbo_rename_raises_after_rename(self):
2552
tt, trans_id = self.create_transform_and_root_trans_id()
2553
parent1 = tt.new_directory('parent1', tt.root)
2554
child1 = tt.new_file('child1', parent1, 'contents')
2555
parent2 = tt.new_directory('parent2', tt.root)
2557
class FakeOSModule(object):
2558
def rename(self, old, new):
2561
self._override_globals_in_method(tt, "_rename_in_limbo",
2562
{"os": FakeOSModule()})
2564
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2565
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2566
self.assertPathExists(path)
2568
self.assertPathDoesNotExist(path)
2569
self.assertPathDoesNotExist(tt._limbodir)
2571
def test_rename_in_limbo_rename_raises_before_rename(self):
2572
tt, trans_id = self.create_transform_and_root_trans_id()
2573
parent1 = tt.new_directory('parent1', tt.root)
2574
child1 = tt.new_file('child1', parent1, 'contents')
2575
parent2 = tt.new_directory('parent2', tt.root)
2577
class FakeOSModule(object):
2578
def rename(self, old, new):
2580
self._override_globals_in_method(tt, "_rename_in_limbo",
2581
{"os": FakeOSModule()})
2583
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2584
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2585
self.assertPathExists(path)
2587
self.assertPathDoesNotExist(path)
2588
self.assertPathDoesNotExist(tt._limbodir)
2591
class TestTransformMissingParent(tests.TestCaseWithTransport):
2593
def make_tt_with_versioned_dir(self):
2594
wt = self.make_branch_and_tree('.')
2595
self.build_tree(['dir/',])
2596
wt.add(['dir'], ['dir-id'])
2597
wt.commit('Create dir')
2598
tt = TreeTransform(wt)
2599
self.addCleanup(tt.finalize)
2602
def test_resolve_create_parent_for_versioned_file(self):
2603
wt, tt = self.make_tt_with_versioned_dir()
2604
dir_tid = tt.trans_id_tree_file_id('dir-id')
2605
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2606
tt.delete_contents(dir_tid)
2607
tt.unversion_file(dir_tid)
2608
conflicts = resolve_conflicts(tt)
2609
# one conflict for the missing directory, one for the unversioned
2611
self.assertLength(2, conflicts)
2613
def test_non_versioned_file_create_conflict(self):
2614
wt, tt = self.make_tt_with_versioned_dir()
2615
dir_tid = tt.trans_id_tree_file_id('dir-id')
2616
tt.new_file('file', dir_tid, 'Contents')
2617
tt.delete_contents(dir_tid)
2618
tt.unversion_file(dir_tid)
2619
conflicts = resolve_conflicts(tt)
2620
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2621
self.assertLength(1, conflicts)
2622
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2626
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2627
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2629
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2630
('', ''), ('directory', 'directory'), (False, False))
2633
class TestTransformPreview(tests.TestCaseWithTransport):
2635
def create_tree(self):
2636
tree = self.make_branch_and_tree('.')
2637
self.build_tree_contents([('a', 'content 1')])
2638
tree.set_root_id('TREE_ROOT')
2639
tree.add('a', 'a-id')
2640
tree.commit('rev1', rev_id='rev1')
2641
return tree.branch.repository.revision_tree('rev1')
2643
def get_empty_preview(self):
2644
repository = self.make_repository('repo')
2645
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2646
preview = TransformPreview(tree)
2647
self.addCleanup(preview.finalize)
2650
def test_transform_preview(self):
2651
revision_tree = self.create_tree()
2652
preview = TransformPreview(revision_tree)
2653
self.addCleanup(preview.finalize)
2655
def test_transform_preview_tree(self):
2656
revision_tree = self.create_tree()
2657
preview = TransformPreview(revision_tree)
2658
self.addCleanup(preview.finalize)
2659
preview.get_preview_tree()
2661
def test_transform_new_file(self):
2662
revision_tree = self.create_tree()
2663
preview = TransformPreview(revision_tree)
2664
self.addCleanup(preview.finalize)
2665
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2666
preview_tree = preview.get_preview_tree()
2667
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2669
preview_tree.get_file('file2-id').read(), 'content B\n')
2671
def test_diff_preview_tree(self):
2672
revision_tree = self.create_tree()
2673
preview = TransformPreview(revision_tree)
2674
self.addCleanup(preview.finalize)
2675
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2676
preview_tree = preview.get_preview_tree()
2678
show_diff_trees(revision_tree, preview_tree, out)
2679
lines = out.getvalue().splitlines()
2680
self.assertEqual(lines[0], "=== added file 'file2'")
2681
# 3 lines of diff administrivia
2682
self.assertEqual(lines[4], "+content B")
2684
def test_transform_conflicts(self):
2685
revision_tree = self.create_tree()
2686
preview = TransformPreview(revision_tree)
2687
self.addCleanup(preview.finalize)
2688
preview.new_file('a', preview.root, 'content 2')
2689
resolve_conflicts(preview)
2690
trans_id = preview.trans_id_file_id('a-id')
2691
self.assertEqual('a.moved', preview.final_name(trans_id))
2693
def get_tree_and_preview_tree(self):
2694
revision_tree = self.create_tree()
2695
preview = TransformPreview(revision_tree)
2696
self.addCleanup(preview.finalize)
2697
a_trans_id = preview.trans_id_file_id('a-id')
2698
preview.delete_contents(a_trans_id)
2699
preview.create_file('b content', a_trans_id)
2700
preview_tree = preview.get_preview_tree()
2701
return revision_tree, preview_tree
2703
def test_iter_changes(self):
2704
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2705
root = revision_tree.inventory.root.file_id
2706
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2707
(root, root), ('a', 'a'), ('file', 'file'),
2709
list(preview_tree.iter_changes(revision_tree)))
2711
def test_include_unchanged_succeeds(self):
2712
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2713
changes = preview_tree.iter_changes(revision_tree,
2714
include_unchanged=True)
2715
root = revision_tree.inventory.root.file_id
2717
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2719
def test_specific_files(self):
2720
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2721
changes = preview_tree.iter_changes(revision_tree,
2722
specific_files=[''])
2723
self.assertEqual([A_ENTRY], list(changes))
2725
def test_want_unversioned(self):
2726
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2727
changes = preview_tree.iter_changes(revision_tree,
2728
want_unversioned=True)
2729
self.assertEqual([A_ENTRY], list(changes))
2731
def test_ignore_extra_trees_no_specific_files(self):
2732
# extra_trees is harmless without specific_files, so we'll silently
2733
# accept it, even though we won't use it.
2734
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2735
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2737
def test_ignore_require_versioned_no_specific_files(self):
2738
# require_versioned is meaningless without specific_files.
2739
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2740
preview_tree.iter_changes(revision_tree, require_versioned=False)
2742
def test_ignore_pb(self):
2743
# pb could be supported, but TT.iter_changes doesn't support it.
2744
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2745
preview_tree.iter_changes(revision_tree)
2747
def test_kind(self):
2748
revision_tree = self.create_tree()
2749
preview = TransformPreview(revision_tree)
2750
self.addCleanup(preview.finalize)
2751
preview.new_file('file', preview.root, 'contents', 'file-id')
2752
preview.new_directory('directory', preview.root, 'dir-id')
2753
preview_tree = preview.get_preview_tree()
2754
self.assertEqual('file', preview_tree.kind('file-id'))
2755
self.assertEqual('directory', preview_tree.kind('dir-id'))
2757
def test_get_file_mtime(self):
2758
preview = self.get_empty_preview()
2759
file_trans_id = preview.new_file('file', preview.root, 'contents',
2761
limbo_path = preview._limbo_name(file_trans_id)
2762
preview_tree = preview.get_preview_tree()
2763
self.assertEqual(os.stat(limbo_path).st_mtime,
2764
preview_tree.get_file_mtime('file-id'))
2766
def test_get_file_mtime_renamed(self):
2767
work_tree = self.make_branch_and_tree('tree')
2768
self.build_tree(['tree/file'])
2769
work_tree.add('file', 'file-id')
2770
preview = TransformPreview(work_tree)
2771
self.addCleanup(preview.finalize)
2772
file_trans_id = preview.trans_id_tree_file_id('file-id')
2773
preview.adjust_path('renamed', preview.root, file_trans_id)
2774
preview_tree = preview.get_preview_tree()
2775
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2776
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2778
def test_get_file_size(self):
2779
work_tree = self.make_branch_and_tree('tree')
2780
self.build_tree_contents([('tree/old', 'old')])
2781
work_tree.add('old', 'old-id')
2782
preview = TransformPreview(work_tree)
2783
self.addCleanup(preview.finalize)
2784
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2786
tree = preview.get_preview_tree()
2787
self.assertEqual(len('old'), tree.get_file_size('old-id'))
2788
self.assertEqual(len('contents'), tree.get_file_size('new-id'))
2790
def test_get_file(self):
2791
preview = self.get_empty_preview()
2792
preview.new_file('file', preview.root, 'contents', 'file-id')
2793
preview_tree = preview.get_preview_tree()
2794
tree_file = preview_tree.get_file('file-id')
2796
self.assertEqual('contents', tree_file.read())
2800
def test_get_symlink_target(self):
2801
self.requireFeature(SymlinkFeature)
2802
preview = self.get_empty_preview()
2803
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2804
preview_tree = preview.get_preview_tree()
2805
self.assertEqual('target',
2806
preview_tree.get_symlink_target('symlink-id'))
2808
def test_all_file_ids(self):
2809
tree = self.make_branch_and_tree('tree')
2810
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2811
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2812
preview = TransformPreview(tree)
2813
self.addCleanup(preview.finalize)
2814
preview.unversion_file(preview.trans_id_file_id('b-id'))
2815
c_trans_id = preview.trans_id_file_id('c-id')
2816
preview.unversion_file(c_trans_id)
2817
preview.version_file('c-id', c_trans_id)
2818
preview_tree = preview.get_preview_tree()
2819
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2820
preview_tree.all_file_ids())
2822
def test_path2id_deleted_unchanged(self):
2823
tree = self.make_branch_and_tree('tree')
2824
self.build_tree(['tree/unchanged', 'tree/deleted'])
2825
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2826
preview = TransformPreview(tree)
2827
self.addCleanup(preview.finalize)
2828
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2829
preview_tree = preview.get_preview_tree()
2830
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2831
self.assertIs(None, preview_tree.path2id('deleted'))
2833
def test_path2id_created(self):
2834
tree = self.make_branch_and_tree('tree')
2835
self.build_tree(['tree/unchanged'])
2836
tree.add(['unchanged'], ['unchanged-id'])
2837
preview = TransformPreview(tree)
2838
self.addCleanup(preview.finalize)
2839
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2840
'contents', 'new-id')
2841
preview_tree = preview.get_preview_tree()
2842
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2844
def test_path2id_moved(self):
2845
tree = self.make_branch_and_tree('tree')
2846
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2847
tree.add(['old_parent', 'old_parent/child'],
2848
['old_parent-id', 'child-id'])
2849
preview = TransformPreview(tree)
2850
self.addCleanup(preview.finalize)
2851
new_parent = preview.new_directory('new_parent', preview.root,
2853
preview.adjust_path('child', new_parent,
2854
preview.trans_id_file_id('child-id'))
2855
preview_tree = preview.get_preview_tree()
2856
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2857
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2859
def test_path2id_renamed_parent(self):
2860
tree = self.make_branch_and_tree('tree')
2861
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2862
tree.add(['old_name', 'old_name/child'],
2863
['parent-id', 'child-id'])
2864
preview = TransformPreview(tree)
2865
self.addCleanup(preview.finalize)
2866
preview.adjust_path('new_name', preview.root,
2867
preview.trans_id_file_id('parent-id'))
2868
preview_tree = preview.get_preview_tree()
2869
self.assertIs(None, preview_tree.path2id('old_name/child'))
2870
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2872
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2873
preview_tree = tt.get_preview_tree()
2874
preview_result = list(preview_tree.iter_entries_by_dir(
2878
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2879
self.assertEqual(actual_result, preview_result)
2881
def test_iter_entries_by_dir_new(self):
2882
tree = self.make_branch_and_tree('tree')
2883
tt = TreeTransform(tree)
2884
tt.new_file('new', tt.root, 'contents', 'new-id')
2885
self.assertMatchingIterEntries(tt)
2887
def test_iter_entries_by_dir_deleted(self):
2888
tree = self.make_branch_and_tree('tree')
2889
self.build_tree(['tree/deleted'])
2890
tree.add('deleted', 'deleted-id')
2891
tt = TreeTransform(tree)
2892
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2893
self.assertMatchingIterEntries(tt)
2895
def test_iter_entries_by_dir_unversioned(self):
2896
tree = self.make_branch_and_tree('tree')
2897
self.build_tree(['tree/removed'])
2898
tree.add('removed', 'removed-id')
2899
tt = TreeTransform(tree)
2900
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2901
self.assertMatchingIterEntries(tt)
2903
def test_iter_entries_by_dir_moved(self):
2904
tree = self.make_branch_and_tree('tree')
2905
self.build_tree(['tree/moved', 'tree/new_parent/'])
2906
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2907
tt = TreeTransform(tree)
2908
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2909
tt.trans_id_file_id('moved-id'))
2910
self.assertMatchingIterEntries(tt)
2912
def test_iter_entries_by_dir_specific_file_ids(self):
2913
tree = self.make_branch_and_tree('tree')
2914
tree.set_root_id('tree-root-id')
2915
self.build_tree(['tree/parent/', 'tree/parent/child'])
2916
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2917
tt = TreeTransform(tree)
2918
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2920
def test_symlink_content_summary(self):
2921
self.requireFeature(SymlinkFeature)
2922
preview = self.get_empty_preview()
2923
preview.new_symlink('path', preview.root, 'target', 'path-id')
2924
summary = preview.get_preview_tree().path_content_summary('path')
2925
self.assertEqual(('symlink', None, None, 'target'), summary)
2927
def test_missing_content_summary(self):
2928
preview = self.get_empty_preview()
2929
summary = preview.get_preview_tree().path_content_summary('path')
2930
self.assertEqual(('missing', None, None, None), summary)
2932
def test_deleted_content_summary(self):
2933
tree = self.make_branch_and_tree('tree')
2934
self.build_tree(['tree/path/'])
2936
preview = TransformPreview(tree)
2937
self.addCleanup(preview.finalize)
2938
preview.delete_contents(preview.trans_id_tree_path('path'))
2939
summary = preview.get_preview_tree().path_content_summary('path')
2940
self.assertEqual(('missing', None, None, None), summary)
2942
def test_file_content_summary_executable(self):
2943
preview = self.get_empty_preview()
2944
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2945
preview.set_executability(True, path_id)
2946
summary = preview.get_preview_tree().path_content_summary('path')
2947
self.assertEqual(4, len(summary))
2948
self.assertEqual('file', summary[0])
2949
# size must be known
2950
self.assertEqual(len('contents'), summary[1])
2952
self.assertEqual(True, summary[2])
2953
# will not have hash (not cheap to determine)
2954
self.assertIs(None, summary[3])
2956
def test_change_executability(self):
2957
tree = self.make_branch_and_tree('tree')
2958
self.build_tree(['tree/path'])
2960
preview = TransformPreview(tree)
2961
self.addCleanup(preview.finalize)
2962
path_id = preview.trans_id_tree_path('path')
2963
preview.set_executability(True, path_id)
2964
summary = preview.get_preview_tree().path_content_summary('path')
2965
self.assertEqual(True, summary[2])
2967
def test_file_content_summary_non_exec(self):
2968
preview = self.get_empty_preview()
2969
preview.new_file('path', preview.root, 'contents', 'path-id')
2970
summary = preview.get_preview_tree().path_content_summary('path')
2971
self.assertEqual(4, len(summary))
2972
self.assertEqual('file', summary[0])
2973
# size must be known
2974
self.assertEqual(len('contents'), summary[1])
2976
self.assertEqual(False, summary[2])
2977
# will not have hash (not cheap to determine)
2978
self.assertIs(None, summary[3])
2980
def test_dir_content_summary(self):
2981
preview = self.get_empty_preview()
2982
preview.new_directory('path', preview.root, 'path-id')
2983
summary = preview.get_preview_tree().path_content_summary('path')
2984
self.assertEqual(('directory', None, None, None), summary)
2986
def test_tree_content_summary(self):
2987
preview = self.get_empty_preview()
2988
path = preview.new_directory('path', preview.root, 'path-id')
2989
preview.set_tree_reference('rev-1', path)
2990
summary = preview.get_preview_tree().path_content_summary('path')
2991
self.assertEqual(4, len(summary))
2992
self.assertEqual('tree-reference', summary[0])
2994
def test_annotate(self):
2995
tree = self.make_branch_and_tree('tree')
2996
self.build_tree_contents([('tree/file', 'a\n')])
2997
tree.add('file', 'file-id')
2998
tree.commit('a', rev_id='one')
2999
self.build_tree_contents([('tree/file', 'a\nb\n')])
3000
preview = TransformPreview(tree)
3001
self.addCleanup(preview.finalize)
3002
file_trans_id = preview.trans_id_file_id('file-id')
3003
preview.delete_contents(file_trans_id)
3004
preview.create_file('a\nb\nc\n', file_trans_id)
3005
preview_tree = preview.get_preview_tree()
3011
annotation = preview_tree.annotate_iter('file-id', 'me:')
3012
self.assertEqual(expected, annotation)
3014
def test_annotate_missing(self):
3015
preview = self.get_empty_preview()
3016
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3017
preview_tree = preview.get_preview_tree()
3023
annotation = preview_tree.annotate_iter('file-id', 'me:')
3024
self.assertEqual(expected, annotation)
3026
def test_annotate_rename(self):
3027
tree = self.make_branch_and_tree('tree')
3028
self.build_tree_contents([('tree/file', 'a\n')])
3029
tree.add('file', 'file-id')
3030
tree.commit('a', rev_id='one')
3031
preview = TransformPreview(tree)
3032
self.addCleanup(preview.finalize)
3033
file_trans_id = preview.trans_id_file_id('file-id')
3034
preview.adjust_path('newname', preview.root, file_trans_id)
3035
preview_tree = preview.get_preview_tree()
3039
annotation = preview_tree.annotate_iter('file-id', 'me:')
3040
self.assertEqual(expected, annotation)
3042
def test_annotate_deleted(self):
3043
tree = self.make_branch_and_tree('tree')
3044
self.build_tree_contents([('tree/file', 'a\n')])
3045
tree.add('file', 'file-id')
3046
tree.commit('a', rev_id='one')
3047
self.build_tree_contents([('tree/file', 'a\nb\n')])
3048
preview = TransformPreview(tree)
3049
self.addCleanup(preview.finalize)
3050
file_trans_id = preview.trans_id_file_id('file-id')
3051
preview.delete_contents(file_trans_id)
3052
preview_tree = preview.get_preview_tree()
3053
annotation = preview_tree.annotate_iter('file-id', 'me:')
3054
self.assertIs(None, annotation)
3056
def test_stored_kind(self):
3057
preview = self.get_empty_preview()
3058
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3059
preview_tree = preview.get_preview_tree()
3060
self.assertEqual('file', preview_tree.stored_kind('file-id'))
3062
def test_is_executable(self):
3063
preview = self.get_empty_preview()
3064
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3065
preview.set_executability(True, preview.trans_id_file_id('file-id'))
3066
preview_tree = preview.get_preview_tree()
3067
self.assertEqual(True, preview_tree.is_executable('file-id'))
3069
def test_get_set_parent_ids(self):
3070
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3071
self.assertEqual([], preview_tree.get_parent_ids())
3072
preview_tree.set_parent_ids(['rev-1'])
3073
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
3075
def test_plan_file_merge(self):
3076
work_a = self.make_branch_and_tree('wta')
3077
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3078
work_a.add('file', 'file-id')
3079
base_id = work_a.commit('base version')
3080
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3081
preview = TransformPreview(work_a)
3082
self.addCleanup(preview.finalize)
3083
trans_id = preview.trans_id_file_id('file-id')
3084
preview.delete_contents(trans_id)
3085
preview.create_file('b\nc\nd\ne\n', trans_id)
3086
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3087
tree_a = preview.get_preview_tree()
3088
tree_a.set_parent_ids([base_id])
3090
('killed-a', 'a\n'),
3091
('killed-b', 'b\n'),
3092
('unchanged', 'c\n'),
3093
('unchanged', 'd\n'),
3096
], list(tree_a.plan_file_merge('file-id', tree_b)))
3098
def test_plan_file_merge_revision_tree(self):
3099
work_a = self.make_branch_and_tree('wta')
3100
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3101
work_a.add('file', 'file-id')
3102
base_id = work_a.commit('base version')
3103
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3104
preview = TransformPreview(work_a.basis_tree())
3105
self.addCleanup(preview.finalize)
3106
trans_id = preview.trans_id_file_id('file-id')
3107
preview.delete_contents(trans_id)
3108
preview.create_file('b\nc\nd\ne\n', trans_id)
3109
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3110
tree_a = preview.get_preview_tree()
3111
tree_a.set_parent_ids([base_id])
3113
('killed-a', 'a\n'),
3114
('killed-b', 'b\n'),
3115
('unchanged', 'c\n'),
3116
('unchanged', 'd\n'),
3119
], list(tree_a.plan_file_merge('file-id', tree_b)))
3121
def test_walkdirs(self):
3122
preview = self.get_empty_preview()
3123
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3124
# FIXME: new_directory should mark root.
3125
preview.fixup_new_roots()
3126
preview_tree = preview.get_preview_tree()
3127
file_trans_id = preview.new_file('a', preview.root, 'contents',
3129
expected = [(('', 'tree-root'),
3130
[('a', 'a', 'file', None, 'a-id', 'file')])]
3131
self.assertEqual(expected, list(preview_tree.walkdirs()))
3133
def test_extras(self):
3134
work_tree = self.make_branch_and_tree('tree')
3135
self.build_tree(['tree/removed-file', 'tree/existing-file',
3136
'tree/not-removed-file'])
3137
work_tree.add(['removed-file', 'not-removed-file'])
3138
preview = TransformPreview(work_tree)
3139
self.addCleanup(preview.finalize)
3140
preview.new_file('new-file', preview.root, 'contents')
3141
preview.new_file('new-versioned-file', preview.root, 'contents',
3143
tree = preview.get_preview_tree()
3144
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3145
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
3148
def test_merge_into_preview(self):
3149
work_tree = self.make_branch_and_tree('tree')
3150
self.build_tree_contents([('tree/file','b\n')])
3151
work_tree.add('file', 'file-id')
3152
work_tree.commit('first commit')
3153
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
3154
self.build_tree_contents([('child/file','b\nc\n')])
3155
child_tree.commit('child commit')
3156
child_tree.lock_write()
3157
self.addCleanup(child_tree.unlock)
3158
work_tree.lock_write()
3159
self.addCleanup(work_tree.unlock)
3160
preview = TransformPreview(work_tree)
3161
self.addCleanup(preview.finalize)
3162
file_trans_id = preview.trans_id_file_id('file-id')
3163
preview.delete_contents(file_trans_id)
3164
preview.create_file('a\nb\n', file_trans_id)
3165
preview_tree = preview.get_preview_tree()
3166
merger = Merger.from_revision_ids(None, preview_tree,
3167
child_tree.branch.last_revision(),
3168
other_branch=child_tree.branch,
3169
tree_branch=work_tree.branch)
3170
merger.merge_type = Merge3Merger
3171
tt = merger.make_merger().make_preview_transform()
3172
self.addCleanup(tt.finalize)
3173
final_tree = tt.get_preview_tree()
3174
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3176
def test_merge_preview_into_workingtree(self):
3177
tree = self.make_branch_and_tree('tree')
3178
tree.set_root_id('TREE_ROOT')
3179
tt = TransformPreview(tree)
3180
self.addCleanup(tt.finalize)
3181
tt.new_file('name', tt.root, 'content', 'file-id')
3182
tree2 = self.make_branch_and_tree('tree2')
3183
tree2.set_root_id('TREE_ROOT')
3184
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3185
None, tree.basis_tree())
3186
merger.merge_type = Merge3Merger
3189
def test_merge_preview_into_workingtree_handles_conflicts(self):
3190
tree = self.make_branch_and_tree('tree')
3191
self.build_tree_contents([('tree/foo', 'bar')])
3192
tree.add('foo', 'foo-id')
3194
tt = TransformPreview(tree)
3195
self.addCleanup(tt.finalize)
3196
trans_id = tt.trans_id_file_id('foo-id')
3197
tt.delete_contents(trans_id)
3198
tt.create_file('baz', trans_id)
3199
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
3200
self.build_tree_contents([('tree2/foo', 'qux')])
3202
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3203
pb, tree.basis_tree())
3204
merger.merge_type = Merge3Merger
3207
def test_has_filename(self):
3208
wt = self.make_branch_and_tree('tree')
3209
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3210
tt = TransformPreview(wt)
3211
removed_id = tt.trans_id_tree_path('removed')
3212
tt.delete_contents(removed_id)
3213
tt.new_file('new', tt.root, 'contents')
3214
modified_id = tt.trans_id_tree_path('modified')
3215
tt.delete_contents(modified_id)
3216
tt.create_file('modified-contents', modified_id)
3217
self.addCleanup(tt.finalize)
3218
tree = tt.get_preview_tree()
3219
self.assertTrue(tree.has_filename('unmodified'))
3220
self.assertFalse(tree.has_filename('not-present'))
3221
self.assertFalse(tree.has_filename('removed'))
3222
self.assertTrue(tree.has_filename('new'))
3223
self.assertTrue(tree.has_filename('modified'))
3225
def test_is_executable(self):
3226
tree = self.make_branch_and_tree('tree')
3227
preview = TransformPreview(tree)
3228
self.addCleanup(preview.finalize)
3229
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3230
preview_tree = preview.get_preview_tree()
3231
self.assertEqual(False, preview_tree.is_executable('baz-id',
3233
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3235
def test_commit_preview_tree(self):
3236
tree = self.make_branch_and_tree('tree')
3237
rev_id = tree.commit('rev1')
3238
tree.branch.lock_write()
3239
self.addCleanup(tree.branch.unlock)
3240
tt = TransformPreview(tree)
3241
tt.new_file('file', tt.root, 'contents', 'file_id')
3242
self.addCleanup(tt.finalize)
3243
preview = tt.get_preview_tree()
3244
preview.set_parent_ids([rev_id])
3245
builder = tree.branch.get_commit_builder([rev_id])
3246
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3247
builder.finish_inventory()
3248
rev2_id = builder.commit('rev2')
3249
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3250
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3252
def test_ascii_limbo_paths(self):
3253
self.requireFeature(tests.UnicodeFilenameFeature)
3254
branch = self.make_branch('any')
3255
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3256
tt = TransformPreview(tree)
3257
self.addCleanup(tt.finalize)
3258
foo_id = tt.new_directory('', ROOT_PARENT)
3259
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3260
limbo_path = tt._limbo_name(bar_id)
3261
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3264
class FakeSerializer(object):
3265
"""Serializer implementation that simply returns the input.
3267
The input is returned in the order used by pack.ContainerPushParser.
3270
def bytes_record(bytes, names):
3274
class TestSerializeTransform(tests.TestCaseWithTransport):
3276
_test_needs_features = [tests.UnicodeFilenameFeature]
3278
def get_preview(self, tree=None):
3280
tree = self.make_branch_and_tree('tree')
3281
tt = TransformPreview(tree)
3282
self.addCleanup(tt.finalize)
3285
def assertSerializesTo(self, expected, tt):
3286
records = list(tt.serialize(FakeSerializer()))
3287
self.assertEqual(expected, records)
3290
def default_attribs():
3295
'_new_executability': {},
3297
'_tree_path_ids': {'': 'new-0'},
3299
'_removed_contents': [],
3300
'_non_present_ids': {},
3303
def make_records(self, attribs, contents):
3305
(((('attribs'),),), bencode.bencode(attribs))]
3306
records.extend([(((n, k),), c) for n, k, c in contents])
3309
def creation_records(self):
3310
attribs = self.default_attribs()
3311
attribs['_id_number'] = 3
3312
attribs['_new_name'] = {
3313
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3314
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3315
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3316
attribs['_new_executability'] = {'new-1': 1}
3318
('new-1', 'file', 'i 1\nbar\n'),
3319
('new-2', 'directory', ''),
3321
return self.make_records(attribs, contents)
3323
def test_serialize_creation(self):
3324
tt = self.get_preview()
3325
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3326
tt.new_directory('qux', tt.root, 'quxx')
3327
self.assertSerializesTo(self.creation_records(), tt)
3329
def test_deserialize_creation(self):
3330
tt = self.get_preview()
3331
tt.deserialize(iter(self.creation_records()))
3332
self.assertEqual(3, tt._id_number)
3333
self.assertEqual({'new-1': u'foo\u1234',
3334
'new-2': 'qux'}, tt._new_name)
3335
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3336
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3337
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3338
self.assertEqual({'new-1': True}, tt._new_executability)
3339
self.assertEqual({'new-1': 'file',
3340
'new-2': 'directory'}, tt._new_contents)
3341
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3343
foo_content = foo_limbo.read()
3346
self.assertEqual('bar', foo_content)
3348
def symlink_creation_records(self):
3349
attribs = self.default_attribs()
3350
attribs['_id_number'] = 2
3351
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3352
attribs['_new_parent'] = {'new-1': 'new-0'}
3353
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3354
return self.make_records(attribs, contents)
3356
def test_serialize_symlink_creation(self):
3357
self.requireFeature(tests.SymlinkFeature)
3358
tt = self.get_preview()
3359
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3360
self.assertSerializesTo(self.symlink_creation_records(), tt)
3362
def test_deserialize_symlink_creation(self):
3363
self.requireFeature(tests.SymlinkFeature)
3364
tt = self.get_preview()
3365
tt.deserialize(iter(self.symlink_creation_records()))
3366
abspath = tt._limbo_name('new-1')
3367
foo_content = osutils.readlink(abspath)
3368
self.assertEqual(u'bar\u1234', foo_content)
3370
def make_destruction_preview(self):
3371
tree = self.make_branch_and_tree('.')
3372
self.build_tree([u'foo\u1234', 'bar'])
3373
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3374
return self.get_preview(tree)
3376
def destruction_records(self):
3377
attribs = self.default_attribs()
3378
attribs['_id_number'] = 3
3379
attribs['_removed_id'] = ['new-1']
3380
attribs['_removed_contents'] = ['new-2']
3381
attribs['_tree_path_ids'] = {
3383
u'foo\u1234'.encode('utf-8'): 'new-1',
3386
return self.make_records(attribs, [])
3388
def test_serialize_destruction(self):
3389
tt = self.make_destruction_preview()
3390
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3391
tt.unversion_file(foo_trans_id)
3392
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3393
tt.delete_contents(bar_trans_id)
3394
self.assertSerializesTo(self.destruction_records(), tt)
3396
def test_deserialize_destruction(self):
3397
tt = self.make_destruction_preview()
3398
tt.deserialize(iter(self.destruction_records()))
3399
self.assertEqual({u'foo\u1234': 'new-1',
3401
'': tt.root}, tt._tree_path_ids)
3402
self.assertEqual({'new-1': u'foo\u1234',
3404
tt.root: ''}, tt._tree_id_paths)
3405
self.assertEqual(set(['new-1']), tt._removed_id)
3406
self.assertEqual(set(['new-2']), tt._removed_contents)
3408
def missing_records(self):
3409
attribs = self.default_attribs()
3410
attribs['_id_number'] = 2
3411
attribs['_non_present_ids'] = {
3413
return self.make_records(attribs, [])
3415
def test_serialize_missing(self):
3416
tt = self.get_preview()
3417
boo_trans_id = tt.trans_id_file_id('boo')
3418
self.assertSerializesTo(self.missing_records(), tt)
3420
def test_deserialize_missing(self):
3421
tt = self.get_preview()
3422
tt.deserialize(iter(self.missing_records()))
3423
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3425
def make_modification_preview(self):
3426
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3427
LINES_TWO = 'z\nbb\nx\ndd\n'
3428
tree = self.make_branch_and_tree('tree')
3429
self.build_tree_contents([('tree/file', LINES_ONE)])
3430
tree.add('file', 'file-id')
3431
return self.get_preview(tree), LINES_TWO
3433
def modification_records(self):
3434
attribs = self.default_attribs()
3435
attribs['_id_number'] = 2
3436
attribs['_tree_path_ids'] = {
3439
attribs['_removed_contents'] = ['new-1']
3440
contents = [('new-1', 'file',
3441
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3442
return self.make_records(attribs, contents)
3444
def test_serialize_modification(self):
3445
tt, LINES = self.make_modification_preview()
3446
trans_id = tt.trans_id_file_id('file-id')
3447
tt.delete_contents(trans_id)
3448
tt.create_file(LINES, trans_id)
3449
self.assertSerializesTo(self.modification_records(), tt)
3451
def test_deserialize_modification(self):
3452
tt, LINES = self.make_modification_preview()
3453
tt.deserialize(iter(self.modification_records()))
3454
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3456
def make_kind_change_preview(self):
3457
LINES = 'a\nb\nc\nd\n'
3458
tree = self.make_branch_and_tree('tree')
3459
self.build_tree(['tree/foo/'])
3460
tree.add('foo', 'foo-id')
3461
return self.get_preview(tree), LINES
3463
def kind_change_records(self):
3464
attribs = self.default_attribs()
3465
attribs['_id_number'] = 2
3466
attribs['_tree_path_ids'] = {
3469
attribs['_removed_contents'] = ['new-1']
3470
contents = [('new-1', 'file',
3471
'i 4\na\nb\nc\nd\n\n')]
3472
return self.make_records(attribs, contents)
3474
def test_serialize_kind_change(self):
3475
tt, LINES = self.make_kind_change_preview()
3476
trans_id = tt.trans_id_file_id('foo-id')
3477
tt.delete_contents(trans_id)
3478
tt.create_file(LINES, trans_id)
3479
self.assertSerializesTo(self.kind_change_records(), tt)
3481
def test_deserialize_kind_change(self):
3482
tt, LINES = self.make_kind_change_preview()
3483
tt.deserialize(iter(self.kind_change_records()))
3484
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3486
def make_add_contents_preview(self):
3487
LINES = 'a\nb\nc\nd\n'
3488
tree = self.make_branch_and_tree('tree')
3489
self.build_tree(['tree/foo'])
3491
os.unlink('tree/foo')
3492
return self.get_preview(tree), LINES
3494
def add_contents_records(self):
3495
attribs = self.default_attribs()
3496
attribs['_id_number'] = 2
3497
attribs['_tree_path_ids'] = {
3500
contents = [('new-1', 'file',
3501
'i 4\na\nb\nc\nd\n\n')]
3502
return self.make_records(attribs, contents)
3504
def test_serialize_add_contents(self):
3505
tt, LINES = self.make_add_contents_preview()
3506
trans_id = tt.trans_id_tree_path('foo')
3507
tt.create_file(LINES, trans_id)
3508
self.assertSerializesTo(self.add_contents_records(), tt)
3510
def test_deserialize_add_contents(self):
3511
tt, LINES = self.make_add_contents_preview()
3512
tt.deserialize(iter(self.add_contents_records()))
3513
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3515
def test_get_parents_lines(self):
3516
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3517
LINES_TWO = 'z\nbb\nx\ndd\n'
3518
tree = self.make_branch_and_tree('tree')
3519
self.build_tree_contents([('tree/file', LINES_ONE)])
3520
tree.add('file', 'file-id')
3521
tt = self.get_preview(tree)
3522
trans_id = tt.trans_id_tree_path('file')
3523
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3524
tt._get_parents_lines(trans_id))
3526
def test_get_parents_texts(self):
3527
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3528
LINES_TWO = 'z\nbb\nx\ndd\n'
3529
tree = self.make_branch_and_tree('tree')
3530
self.build_tree_contents([('tree/file', LINES_ONE)])
3531
tree.add('file', 'file-id')
3532
tt = self.get_preview(tree)
3533
trans_id = tt.trans_id_tree_path('file')
3534
self.assertEqual((LINES_ONE,),
3535
tt._get_parents_texts(trans_id))
3538
class TestOrphan(tests.TestCaseWithTransport):
3540
def test_no_orphan_for_transform_preview(self):
3541
tree = self.make_branch_and_tree('tree')
3542
tt = transform.TransformPreview(tree)
3543
self.addCleanup(tt.finalize)
3544
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3546
def _set_orphan_policy(self, wt, policy):
3547
wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
3550
def _prepare_orphan(self, wt):
3551
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3552
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3553
wt.commit('add dir and file ignoring foo')
3554
tt = transform.TreeTransform(wt)
3555
self.addCleanup(tt.finalize)
3556
# dir and bar are deleted
3557
dir_tid = tt.trans_id_tree_path('dir')
3558
file_tid = tt.trans_id_tree_path('dir/file')
3559
orphan_tid = tt.trans_id_tree_path('dir/foo')
3560
tt.delete_contents(file_tid)
3561
tt.unversion_file(file_tid)
3562
tt.delete_contents(dir_tid)
3563
tt.unversion_file(dir_tid)
3564
# There should be a conflict because dir still contain foo
3565
raw_conflicts = tt.find_conflicts()
3566
self.assertLength(1, raw_conflicts)
3567
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3568
return tt, orphan_tid
3570
def test_new_orphan_created(self):
3571
wt = self.make_branch_and_tree('.')
3572
self._set_orphan_policy(wt, 'move')
3573
tt, orphan_tid = self._prepare_orphan(wt)
3576
warnings.append(args[0] % args[1:])
3577
self.overrideAttr(trace, 'warning', warning)
3578
remaining_conflicts = resolve_conflicts(tt)
3579
self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
3581
# Yeah for resolved conflicts !
3582
self.assertLength(0, remaining_conflicts)
3583
# We have a new orphan
3584
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3585
self.assertEquals('bzr-orphans',
3586
tt.final_name(tt.final_parent(orphan_tid)))
3588
def test_never_orphan(self):
3589
wt = self.make_branch_and_tree('.')
3590
self._set_orphan_policy(wt, 'conflict')
3591
tt, orphan_tid = self._prepare_orphan(wt)
3592
remaining_conflicts = resolve_conflicts(tt)
3593
self.assertLength(1, remaining_conflicts)
3594
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3595
remaining_conflicts.pop())
3597
def test_orphan_error(self):
3598
def bogus_orphan(tt, orphan_id, parent_id):
3599
raise transform.OrphaningError(tt.final_name(orphan_id),
3600
tt.final_name(parent_id))
3601
transform.orphaning_registry.register('bogus', bogus_orphan,
3602
'Raise an error when orphaning')
3603
wt = self.make_branch_and_tree('.')
3604
self._set_orphan_policy(wt, 'bogus')
3605
tt, orphan_tid = self._prepare_orphan(wt)
3606
remaining_conflicts = resolve_conflicts(tt)
3607
self.assertLength(1, remaining_conflicts)
3608
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3609
remaining_conflicts.pop())
3611
def test_unknown_orphan_policy(self):
3612
wt = self.make_branch_and_tree('.')
3613
# Set a fictional policy nobody ever implemented
3614
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3615
tt, orphan_tid = self._prepare_orphan(wt)
3618
warnings.append(args[0] % args[1:])
3619
self.overrideAttr(trace, 'warning', warning)
3620
remaining_conflicts = resolve_conflicts(tt)
3621
# We fallback to the default policy which create a conflict
3622
self.assertLength(1, remaining_conflicts)
3623
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3624
remaining_conflicts.pop())
3625
self.assertLength(1, warnings)
3626
self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')