479
957
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')
960
def test_rename_fails(self):
961
self.requireFeature(features.not_running_as_root)
962
# see https://bugs.launchpad.net/bzr/+bug/491763
963
create, root_id = self.get_transform()
964
first_dir = create.new_directory('first-dir', root_id, 'first-id')
965
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,
968
if os.name == "posix" and sys.platform != "cygwin":
969
# posix filesystems fail on renaming if the readonly bit is set
970
osutils.make_readonly(self.wt.abspath('first-dir'))
971
elif os.name == "nt":
972
# windows filesystems fail on renaming open files
973
self.addCleanup(file(self.wt.abspath('myfile')).close)
975
self.skip("Don't know how to force a permissions error on rename")
976
# now transform to rename
977
rename_transform, root_id = self.get_transform()
978
file_trans_id = rename_transform.trans_id_file_id('myfile-id')
979
dir_id = rename_transform.trans_id_file_id('first-id')
980
rename_transform.adjust_path('newname', dir_id, file_trans_id)
981
e = self.assertRaises(errors.TransformRenameFailed,
982
rename_transform.apply)
984
# "Failed to rename .../work/.bzr/checkout/limbo/new-1
985
# to .../first-dir/newname: [Errno 13] Permission denied"
986
# On windows looks like:
987
# "Failed to rename .../work/myfile to
988
# .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
989
# This test isn't concerned with exactly what the error looks like,
990
# and the strerror will vary across OS and locales, but the assert
991
# that the exeception attributes are what we expect
992
self.assertEqual(e.errno, errno.EACCES)
993
if os.name == "posix":
994
self.assertEndsWith(e.to_path, "/first-dir/newname")
996
self.assertEqual(os.path.basename(e.from_path), "myfile")
998
def test_set_executability_order(self):
999
"""Ensure that executability behaves the same, no matter what order.
1001
- create file and set executability simultaneously
1002
- create file and set executability afterward
1003
- unsetting the executability of a file whose executability has not been
1004
declared should throw an exception (this may happen when a
1005
merge attempts to create a file with a duplicate ID)
1007
transform, root = self.get_transform()
1008
wt = transform._tree
1010
self.addCleanup(wt.unlock)
1011
transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
1013
sac = transform.new_file('set_after_creation', root,
1014
'Set after creation', 'sac')
1015
transform.set_executability(True, sac)
1016
uws = transform.new_file('unset_without_set', root, 'Unset badly',
1018
self.assertRaises(KeyError, transform.set_executability, None, uws)
1020
self.assertTrue(wt.is_executable('soc'))
1021
self.assertTrue(wt.is_executable('sac'))
1023
def test_preserve_mode(self):
1024
"""File mode is preserved when replacing content"""
1025
if sys.platform == 'win32':
1026
raise TestSkipped('chmod has no effect on win32')
1027
transform, root = self.get_transform()
1028
transform.new_file('file1', root, 'contents', 'file1-id', True)
1030
self.wt.lock_write()
1031
self.addCleanup(self.wt.unlock)
1032
self.assertTrue(self.wt.is_executable('file1-id'))
1033
transform, root = self.get_transform()
1034
file1_id = transform.trans_id_tree_file_id('file1-id')
1035
transform.delete_contents(file1_id)
1036
transform.create_file('contents2', file1_id)
1038
self.assertTrue(self.wt.is_executable('file1-id'))
1040
def test__set_mode_stats_correctly(self):
1041
"""_set_mode stats to determine file mode."""
1042
if sys.platform == 'win32':
1043
raise TestSkipped('chmod has no effect on win32')
1047
def instrumented_stat(path):
1048
stat_paths.append(path)
1049
return real_stat(path)
1051
transform, root = self.get_transform()
1053
bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
1054
file_id='bar-id-1', executable=False)
1057
transform, root = self.get_transform()
1058
bar1_id = transform.trans_id_tree_path('bar')
1059
bar2_id = transform.trans_id_tree_path('bar2')
1061
os.stat = instrumented_stat
1062
transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
1065
transform.finalize()
1067
bar1_abspath = self.wt.abspath('bar')
1068
self.assertEqual([bar1_abspath], stat_paths)
1070
def test_iter_changes(self):
1071
self.wt.set_root_id('eert_toor')
1072
transform, root = self.get_transform()
1073
transform.new_file('old', root, 'blah', 'id-1', True)
1075
transform, root = self.get_transform()
1077
self.assertEqual([], list(transform.iter_changes()))
1078
old = transform.trans_id_tree_file_id('id-1')
1079
transform.unversion_file(old)
1080
self.assertEqual([('id-1', ('old', None), False, (True, False),
1081
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1082
(True, True))], list(transform.iter_changes()))
1083
transform.new_directory('new', root, 'id-1')
1084
self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
1085
('eert_toor', 'eert_toor'), ('old', 'new'),
1086
('file', 'directory'),
1087
(True, False))], list(transform.iter_changes()))
1089
transform.finalize()
1091
def test_iter_changes_new(self):
1092
self.wt.set_root_id('eert_toor')
1093
transform, root = self.get_transform()
1094
transform.new_file('old', root, 'blah')
1096
transform, root = self.get_transform()
1098
old = transform.trans_id_tree_path('old')
1099
transform.version_file('id-1', old)
1100
self.assertEqual([('id-1', (None, 'old'), False, (False, True),
1101
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1102
(False, False))], list(transform.iter_changes()))
1104
transform.finalize()
1106
def test_iter_changes_modifications(self):
1107
self.wt.set_root_id('eert_toor')
1108
transform, root = self.get_transform()
1109
transform.new_file('old', root, 'blah', 'id-1')
1110
transform.new_file('new', root, 'blah')
1111
transform.new_directory('subdir', root, 'subdir-id')
1113
transform, root = self.get_transform()
1115
old = transform.trans_id_tree_path('old')
1116
subdir = transform.trans_id_tree_file_id('subdir-id')
1117
new = transform.trans_id_tree_path('new')
1118
self.assertEqual([], list(transform.iter_changes()))
1121
transform.delete_contents(old)
1122
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1123
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
1124
(False, False))], list(transform.iter_changes()))
1127
transform.create_file('blah', old)
1128
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1129
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1130
(False, False))], list(transform.iter_changes()))
1131
transform.cancel_deletion(old)
1132
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1133
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1134
(False, False))], list(transform.iter_changes()))
1135
transform.cancel_creation(old)
1137
# move file_id to a different file
1138
self.assertEqual([], list(transform.iter_changes()))
1139
transform.unversion_file(old)
1140
transform.version_file('id-1', new)
1141
transform.adjust_path('old', root, new)
1142
self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
1143
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1144
(False, False))], list(transform.iter_changes()))
1145
transform.cancel_versioning(new)
1146
transform._removed_id = set()
1149
self.assertEqual([], list(transform.iter_changes()))
1150
transform.set_executability(True, old)
1151
self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
1152
('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
1153
(False, True))], list(transform.iter_changes()))
1154
transform.set_executability(None, old)
1157
self.assertEqual([], list(transform.iter_changes()))
1158
transform.adjust_path('new', root, old)
1159
transform._new_parent = {}
1160
self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
1161
('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
1162
(False, False))], list(transform.iter_changes()))
1163
transform._new_name = {}
1166
self.assertEqual([], list(transform.iter_changes()))
1167
transform.adjust_path('new', subdir, old)
1168
transform._new_name = {}
1169
self.assertEqual([('id-1', ('old', 'subdir/old'), False,
1170
(True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
1171
('file', 'file'), (False, False))],
1172
list(transform.iter_changes()))
1173
transform._new_path = {}
1176
transform.finalize()
1178
def test_iter_changes_modified_bleed(self):
1179
self.wt.set_root_id('eert_toor')
1180
"""Modified flag should not bleed from one change to another"""
1181
# unfortunately, we have no guarantee that file1 (which is modified)
1182
# will be applied before file2. And if it's applied after file2, it
1183
# obviously can't bleed into file2's change output. But for now, it
1185
transform, root = self.get_transform()
1186
transform.new_file('file1', root, 'blah', 'id-1')
1187
transform.new_file('file2', root, 'blah', 'id-2')
1189
transform, root = self.get_transform()
1191
transform.delete_contents(transform.trans_id_file_id('id-1'))
1192
transform.set_executability(True,
1193
transform.trans_id_file_id('id-2'))
1194
self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
1195
('eert_toor', 'eert_toor'), ('file1', u'file1'),
1196
('file', None), (False, False)),
1197
('id-2', (u'file2', u'file2'), False, (True, True),
1198
('eert_toor', 'eert_toor'), ('file2', u'file2'),
1199
('file', 'file'), (False, True))],
1200
list(transform.iter_changes()))
1202
transform.finalize()
1204
def test_iter_changes_move_missing(self):
1205
"""Test moving ids with no files around"""
1206
self.wt.set_root_id('toor_eert')
1207
# Need two steps because versioning a non-existant file is a conflict.
1208
transform, root = self.get_transform()
1209
transform.new_directory('floater', root, 'floater-id')
1211
transform, root = self.get_transform()
1212
transform.delete_contents(transform.trans_id_tree_path('floater'))
1214
transform, root = self.get_transform()
1215
floater = transform.trans_id_tree_path('floater')
1217
transform.adjust_path('flitter', root, floater)
1218
self.assertEqual([('floater-id', ('floater', 'flitter'), False,
1219
(True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
1220
(None, None), (False, False))], list(transform.iter_changes()))
1222
transform.finalize()
1224
def test_iter_changes_pointless(self):
1225
"""Ensure that no-ops are not treated as modifications"""
1226
self.wt.set_root_id('eert_toor')
1227
transform, root = self.get_transform()
1228
transform.new_file('old', root, 'blah', 'id-1')
1229
transform.new_directory('subdir', root, 'subdir-id')
1231
transform, root = self.get_transform()
1233
old = transform.trans_id_tree_path('old')
1234
subdir = transform.trans_id_tree_file_id('subdir-id')
1235
self.assertEqual([], list(transform.iter_changes()))
1236
transform.delete_contents(subdir)
1237
transform.create_directory(subdir)
1238
transform.set_executability(False, old)
1239
transform.unversion_file(old)
1240
transform.version_file('id-1', old)
1241
transform.adjust_path('old', root, old)
1242
self.assertEqual([], list(transform.iter_changes()))
1244
transform.finalize()
1246
def test_rename_count(self):
1247
transform, root = self.get_transform()
1248
transform.new_file('name1', root, 'contents')
1249
self.assertEqual(transform.rename_count, 0)
1251
self.assertEqual(transform.rename_count, 1)
1252
transform2, root = self.get_transform()
1253
transform2.adjust_path('name2', root,
1254
transform2.trans_id_tree_path('name1'))
1255
self.assertEqual(transform2.rename_count, 0)
1257
self.assertEqual(transform2.rename_count, 2)
1259
def test_change_parent(self):
1260
"""Ensure that after we change a parent, the results are still right.
1262
Renames and parent changes on pending transforms can happen as part
1263
of conflict resolution, and are explicitly permitted by the
1266
This test ensures they work correctly with the rename-avoidance
1269
transform, root = self.get_transform()
1270
parent1 = transform.new_directory('parent1', root)
1271
child1 = transform.new_file('child1', parent1, 'contents')
1272
parent2 = transform.new_directory('parent2', root)
1273
transform.adjust_path('child1', parent2, child1)
1275
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1276
self.assertPathExists(self.wt.abspath('parent2/child1'))
1277
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1278
# no rename for child1 (counting only renames during apply)
1279
self.assertEqual(2, transform.rename_count)
1281
def test_cancel_parent(self):
1282
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1284
This is like the test_change_parent, except that we cancel the parent
1285
before adjusting the path. The transform must detect that the
1286
directory is non-empty, and move children to safe locations.
1288
transform, root = self.get_transform()
1289
parent1 = transform.new_directory('parent1', root)
1290
child1 = transform.new_file('child1', parent1, 'contents')
1291
child2 = transform.new_file('child2', parent1, 'contents')
1293
transform.cancel_creation(parent1)
1295
self.fail('Failed to move child1 before deleting parent1')
1296
transform.cancel_creation(child2)
1297
transform.create_directory(parent1)
1299
transform.cancel_creation(parent1)
1300
# If the transform incorrectly believes that child2 is still in
1301
# parent1's limbo directory, it will try to rename it and fail
1302
# because was already moved by the first cancel_creation.
1304
self.fail('Transform still thinks child2 is a child of parent1')
1305
parent2 = transform.new_directory('parent2', root)
1306
transform.adjust_path('child1', parent2, child1)
1308
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1309
self.assertPathExists(self.wt.abspath('parent2/child1'))
1310
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1311
self.assertEqual(2, transform.rename_count)
1313
def test_adjust_and_cancel(self):
1314
"""Make sure adjust_path keeps track of limbo children properly"""
1315
transform, root = self.get_transform()
1316
parent1 = transform.new_directory('parent1', root)
1317
child1 = transform.new_file('child1', parent1, 'contents')
1318
parent2 = transform.new_directory('parent2', root)
1319
transform.adjust_path('child1', parent2, child1)
1320
transform.cancel_creation(child1)
1322
transform.cancel_creation(parent1)
1323
# if the transform thinks child1 is still in parent1's limbo
1324
# directory, it will attempt to move it and fail.
1326
self.fail('Transform still thinks child1 is a child of parent1')
1327
transform.finalize()
1329
def test_noname_contents(self):
1330
"""TreeTransform should permit deferring naming files."""
1331
transform, root = self.get_transform()
1332
parent = transform.trans_id_file_id('parent-id')
1334
transform.create_directory(parent)
1336
self.fail("Can't handle contents with no name")
1337
transform.finalize()
1339
def test_noname_contents_nested(self):
1340
"""TreeTransform should permit deferring naming files."""
1341
transform, root = self.get_transform()
1342
parent = transform.trans_id_file_id('parent-id')
1344
transform.create_directory(parent)
1346
self.fail("Can't handle contents with no name")
1347
child = transform.new_directory('child', parent)
1348
transform.adjust_path('parent', root, parent)
1350
self.assertPathExists(self.wt.abspath('parent/child'))
1351
self.assertEqual(1, transform.rename_count)
1353
def test_reuse_name(self):
1354
"""Avoid reusing the same limbo name for different files"""
1355
transform, root = self.get_transform()
1356
parent = transform.new_directory('parent', root)
1357
child1 = transform.new_directory('child', parent)
1359
child2 = transform.new_directory('child', parent)
1361
self.fail('Tranform tried to use the same limbo name twice')
1362
transform.adjust_path('child2', parent, child2)
1364
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1365
# child2 is put into top-level limbo because child1 has already
1366
# claimed the direct limbo path when child2 is created. There is no
1367
# advantage in renaming files once they're in top-level limbo, except
1369
self.assertEqual(2, transform.rename_count)
1371
def test_reuse_when_first_moved(self):
1372
"""Don't avoid direct paths when it is safe to use them"""
1373
transform, root = self.get_transform()
1374
parent = transform.new_directory('parent', root)
1375
child1 = transform.new_directory('child', parent)
1376
transform.adjust_path('child1', parent, child1)
1377
child2 = transform.new_directory('child', parent)
1379
# limbo/new-1 => parent
1380
self.assertEqual(1, transform.rename_count)
1382
def test_reuse_after_cancel(self):
1383
"""Don't avoid direct paths when it is safe to use them"""
1384
transform, root = self.get_transform()
1385
parent2 = transform.new_directory('parent2', root)
1386
child1 = transform.new_directory('child1', parent2)
1387
transform.cancel_creation(parent2)
1388
transform.create_directory(parent2)
1389
child2 = transform.new_directory('child1', parent2)
1390
transform.adjust_path('child2', parent2, child1)
1392
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1393
self.assertEqual(2, transform.rename_count)
1395
def test_finalize_order(self):
1396
"""Finalize must be done in child-to-parent order"""
1397
transform, root = self.get_transform()
1398
parent = transform.new_directory('parent', root)
1399
child = transform.new_directory('child', parent)
1401
transform.finalize()
1403
self.fail('Tried to remove parent before child1')
1405
def test_cancel_with_cancelled_child_should_succeed(self):
1406
transform, root = self.get_transform()
1407
parent = transform.new_directory('parent', root)
1408
child = transform.new_directory('child', parent)
1409
transform.cancel_creation(child)
1410
transform.cancel_creation(parent)
1411
transform.finalize()
1413
def test_rollback_on_directory_clash(self):
1415
wt = self.make_branch_and_tree('.')
1416
tt = TreeTransform(wt) # TreeTransform obtains write lock
1418
foo = tt.new_directory('foo', tt.root)
1419
tt.new_file('bar', foo, 'foobar')
1420
baz = tt.new_directory('baz', tt.root)
1421
tt.new_file('qux', baz, 'quux')
1422
# Ask for a rename 'foo' -> 'baz'
1423
tt.adjust_path('baz', tt.root, foo)
1424
# Lie to tt that we've already resolved all conflicts.
1425
tt.apply(no_conflicts=True)
1429
# The rename will fail because the target directory is not empty (but
1430
# raises FileExists anyway).
1431
err = self.assertRaises(errors.FileExists, tt_helper)
1432
self.assertContainsRe(str(err),
1433
"^File exists: .+/baz")
1435
def test_two_directories_clash(self):
1437
wt = self.make_branch_and_tree('.')
1438
tt = TreeTransform(wt) # TreeTransform obtains write lock
1440
foo_1 = tt.new_directory('foo', tt.root)
1441
tt.new_directory('bar', foo_1)
1442
# Adding the same directory with a different content
1443
foo_2 = tt.new_directory('foo', tt.root)
1444
tt.new_directory('baz', foo_2)
1445
# Lie to tt that we've already resolved all conflicts.
1446
tt.apply(no_conflicts=True)
1450
err = self.assertRaises(errors.FileExists, tt_helper)
1451
self.assertContainsRe(str(err),
1452
"^File exists: .+/foo")
1454
def test_two_directories_clash_finalize(self):
1456
wt = self.make_branch_and_tree('.')
1457
tt = TreeTransform(wt) # TreeTransform obtains write lock
1459
foo_1 = tt.new_directory('foo', tt.root)
1460
tt.new_directory('bar', foo_1)
1461
# Adding the same directory with a different content
1462
foo_2 = tt.new_directory('foo', tt.root)
1463
tt.new_directory('baz', foo_2)
1464
# Lie to tt that we've already resolved all conflicts.
1465
tt.apply(no_conflicts=True)
1469
err = self.assertRaises(errors.FileExists, tt_helper)
1470
self.assertContainsRe(str(err),
1471
"^File exists: .+/foo")
1473
def test_file_to_directory(self):
1474
wt = self.make_branch_and_tree('.')
1475
self.build_tree(['foo'])
1478
tt = TreeTransform(wt)
1479
self.addCleanup(tt.finalize)
1480
foo_trans_id = tt.trans_id_tree_path("foo")
1481
tt.delete_contents(foo_trans_id)
1482
tt.create_directory(foo_trans_id)
1483
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1484
tt.create_file(["aa\n"], bar_trans_id)
1485
tt.version_file("bar-1", bar_trans_id)
1487
self.assertPathExists("foo/bar")
1490
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1495
changes = wt.changes_from(wt.basis_tree())
1496
self.assertFalse(changes.has_changed(), changes)
1498
def test_file_to_symlink(self):
1499
self.requireFeature(SymlinkFeature)
1500
wt = self.make_branch_and_tree('.')
1501
self.build_tree(['foo'])
1504
tt = TreeTransform(wt)
1505
self.addCleanup(tt.finalize)
1506
foo_trans_id = tt.trans_id_tree_path("foo")
1507
tt.delete_contents(foo_trans_id)
1508
tt.create_symlink("bar", foo_trans_id)
1510
self.assertPathExists("foo")
1512
self.addCleanup(wt.unlock)
1513
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1516
def test_dir_to_file(self):
1517
wt = self.make_branch_and_tree('.')
1518
self.build_tree(['foo/', 'foo/bar'])
1519
wt.add(['foo', 'foo/bar'])
1521
tt = TreeTransform(wt)
1522
self.addCleanup(tt.finalize)
1523
foo_trans_id = tt.trans_id_tree_path("foo")
1524
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1525
tt.delete_contents(foo_trans_id)
1526
tt.delete_versioned(bar_trans_id)
1527
tt.create_file(["aa\n"], foo_trans_id)
1529
self.assertPathExists("foo")
1531
self.addCleanup(wt.unlock)
1532
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1535
def test_dir_to_hardlink(self):
1536
self.requireFeature(HardlinkFeature)
1537
wt = self.make_branch_and_tree('.')
1538
self.build_tree(['foo/', 'foo/bar'])
1539
wt.add(['foo', 'foo/bar'])
1541
tt = TreeTransform(wt)
1542
self.addCleanup(tt.finalize)
1543
foo_trans_id = tt.trans_id_tree_path("foo")
1544
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1545
tt.delete_contents(foo_trans_id)
1546
tt.delete_versioned(bar_trans_id)
1547
self.build_tree(['baz'])
1548
tt.create_hardlink("baz", foo_trans_id)
1550
self.assertPathExists("foo")
1551
self.assertPathExists("baz")
1553
self.addCleanup(wt.unlock)
1554
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1557
def test_no_final_path(self):
1558
transform, root = self.get_transform()
1559
trans_id = transform.trans_id_file_id('foo')
1560
transform.create_file('bar', trans_id)
1561
transform.cancel_creation(trans_id)
1564
def test_create_from_tree(self):
1565
tree1 = self.make_branch_and_tree('tree1')
1566
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1567
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1568
tree2 = self.make_branch_and_tree('tree2')
1569
tt = TreeTransform(tree2)
1570
foo_trans_id = tt.create_path('foo', tt.root)
1571
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1572
bar_trans_id = tt.create_path('bar', tt.root)
1573
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1575
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1576
self.assertFileEqual('baz', 'tree2/bar')
1578
def test_create_from_tree_bytes(self):
1579
"""Provided lines are used instead of tree content."""
1580
tree1 = self.make_branch_and_tree('tree1')
1581
self.build_tree_contents([('tree1/foo', 'bar'),])
1582
tree1.add('foo', 'foo-id')
1583
tree2 = self.make_branch_and_tree('tree2')
1584
tt = TreeTransform(tree2)
1585
foo_trans_id = tt.create_path('foo', tt.root)
1586
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1588
self.assertFileEqual('qux', 'tree2/foo')
1590
def test_create_from_tree_symlink(self):
1591
self.requireFeature(SymlinkFeature)
1592
tree1 = self.make_branch_and_tree('tree1')
1593
os.symlink('bar', 'tree1/foo')
1594
tree1.add('foo', 'foo-id')
1595
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1596
foo_trans_id = tt.create_path('foo', tt.root)
1597
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1599
self.assertEqual('bar', os.readlink('tree2/foo'))
494
1602
class TransformGroup(object):
495
def __init__(self, dirname):
1604
def __init__(self, dirname, root_id):
496
1605
self.name = dirname
497
1606
os.mkdir(dirname)
498
1607
self.wt = BzrDir.create_standalone_workingtree(dirname)
1608
self.wt.set_root_id(root_id)
499
1609
self.b = self.wt.branch
500
1610
self.tt = TreeTransform(self.wt)
501
1611
self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
503
1614
def conflict_text(tree, merge):
504
1615
template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
505
1616
return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1619
class TestInventoryAltered(tests.TestCaseWithTransport):
1621
def test_inventory_altered_unchanged(self):
1622
tree = self.make_branch_and_tree('tree')
1623
self.build_tree(['tree/foo'])
1624
tree.add('foo', 'foo-id')
1625
with TransformPreview(tree) as tt:
1626
self.assertEqual([], tt._inventory_altered())
1628
def test_inventory_altered_changed_parent_id(self):
1629
tree = self.make_branch_and_tree('tree')
1630
self.build_tree(['tree/foo'])
1631
tree.add('foo', 'foo-id')
1632
with TransformPreview(tree) as tt:
1633
tt.unversion_file(tt.root)
1634
tt.version_file('new-id', tt.root)
1635
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1636
foo_tuple = ('foo', foo_trans_id)
1637
root_tuple = ('', tt.root)
1638
self.assertEqual([root_tuple, foo_tuple], tt._inventory_altered())
1640
def test_inventory_altered_noop_changed_parent_id(self):
1641
tree = self.make_branch_and_tree('tree')
1642
self.build_tree(['tree/foo'])
1643
tree.add('foo', 'foo-id')
1644
with TransformPreview(tree) as tt:
1645
tt.unversion_file(tt.root)
1646
tt.version_file(tree.get_root_id(), tt.root)
1647
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
1648
self.assertEqual([], tt._inventory_altered())
508
1651
class TestTransformMerge(TestCaseInTempDir):
509
1653
def test_text_merge(self):
510
base = TransformGroup("base")
1654
root_id = generate_ids.gen_root_id()
1655
base = TransformGroup("base", root_id)
511
1656
base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
512
1657
base.tt.new_file('b', base.root, 'b1', 'b')
513
1658
base.tt.new_file('c', base.root, 'c', 'c')
698
1847
a.add(['foo', 'foo/bar', 'foo/baz'])
699
1848
a.commit('initial commit')
700
1849
b = BzrDir.create_standalone_workingtree('b')
701
build_tree(a.basis_tree(), b)
1850
basis = a.basis_tree()
1852
self.addCleanup(basis.unlock)
1853
build_tree(basis, b)
702
1854
self.assertIs(os.path.isdir('b/foo'), True)
703
1855
self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
704
1856
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):
1858
def test_build_with_references(self):
1859
tree = self.make_branch_and_tree('source',
1860
format='dirstate-with-subtree')
1861
subtree = self.make_branch_and_tree('source/subtree',
1862
format='dirstate-with-subtree')
1863
tree.add_reference(subtree)
1864
tree.commit('a revision')
1865
tree.branch.create_checkout('target')
1866
self.assertPathExists('target')
1867
self.assertPathExists('target/subtree')
1869
def test_file_conflict_handling(self):
1870
"""Ensure that when building trees, conflict handling is done"""
1871
source = self.make_branch_and_tree('source')
1872
target = self.make_branch_and_tree('target')
1873
self.build_tree(['source/file', 'target/file'])
1874
source.add('file', 'new-file')
1875
source.commit('added file')
1876
build_tree(source.basis_tree(), target)
1877
self.assertEqual([DuplicateEntry('Moved existing file to',
1878
'file.moved', 'file', None, 'new-file')],
1880
target2 = self.make_branch_and_tree('target2')
1881
target_file = file('target2/file', 'wb')
1883
source_file = file('source/file', 'rb')
1885
target_file.write(source_file.read())
1890
build_tree(source.basis_tree(), target2)
1891
self.assertEqual([], target2.conflicts())
1893
def test_symlink_conflict_handling(self):
1894
"""Ensure that when building trees, conflict handling is done"""
1895
self.requireFeature(SymlinkFeature)
1896
source = self.make_branch_and_tree('source')
1897
os.symlink('foo', 'source/symlink')
1898
source.add('symlink', 'new-symlink')
1899
source.commit('added file')
1900
target = self.make_branch_and_tree('target')
1901
os.symlink('bar', 'target/symlink')
1902
build_tree(source.basis_tree(), target)
1903
self.assertEqual([DuplicateEntry('Moved existing file to',
1904
'symlink.moved', 'symlink', None, 'new-symlink')],
1906
target = self.make_branch_and_tree('target2')
1907
os.symlink('foo', 'target2/symlink')
1908
build_tree(source.basis_tree(), target)
1909
self.assertEqual([], target.conflicts())
1911
def test_directory_conflict_handling(self):
1912
"""Ensure that when building trees, conflict handling is done"""
1913
source = self.make_branch_and_tree('source')
1914
target = self.make_branch_and_tree('target')
1915
self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1916
source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1917
source.commit('added file')
1918
build_tree(source.basis_tree(), target)
1919
self.assertEqual([], target.conflicts())
1920
self.assertPathExists('target/dir1/file')
1922
# Ensure contents are merged
1923
target = self.make_branch_and_tree('target2')
1924
self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1925
build_tree(source.basis_tree(), target)
1926
self.assertEqual([], target.conflicts())
1927
self.assertPathExists('target2/dir1/file2')
1928
self.assertPathExists('target2/dir1/file')
1930
# Ensure new contents are suppressed for existing branches
1931
target = self.make_branch_and_tree('target3')
1932
self.make_branch('target3/dir1')
1933
self.build_tree(['target3/dir1/file2'])
1934
build_tree(source.basis_tree(), target)
1935
self.assertPathDoesNotExist('target3/dir1/file')
1936
self.assertPathExists('target3/dir1/file2')
1937
self.assertPathExists('target3/dir1.diverted/file')
1938
self.assertEqual([DuplicateEntry('Diverted to',
1939
'dir1.diverted', 'dir1', 'new-dir1', None)],
1942
target = self.make_branch_and_tree('target4')
1943
self.build_tree(['target4/dir1/'])
1944
self.make_branch('target4/dir1/file')
1945
build_tree(source.basis_tree(), target)
1946
self.assertPathExists('target4/dir1/file')
1947
self.assertEqual('directory', file_kind('target4/dir1/file'))
1948
self.assertPathExists('target4/dir1/file.diverted')
1949
self.assertEqual([DuplicateEntry('Diverted to',
1950
'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1953
def test_mixed_conflict_handling(self):
1954
"""Ensure that when building trees, conflict handling is done"""
1955
source = self.make_branch_and_tree('source')
1956
target = self.make_branch_and_tree('target')
1957
self.build_tree(['source/name', 'target/name/'])
1958
source.add('name', 'new-name')
1959
source.commit('added file')
1960
build_tree(source.basis_tree(), target)
1961
self.assertEqual([DuplicateEntry('Moved existing file to',
1962
'name.moved', 'name', None, 'new-name')], target.conflicts())
1964
def test_raises_in_populated(self):
1965
source = self.make_branch_and_tree('source')
1966
self.build_tree(['source/name'])
1968
source.commit('added name')
1969
target = self.make_branch_and_tree('target')
1970
self.build_tree(['target/name'])
1972
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1973
build_tree, source.basis_tree(), target)
1975
def test_build_tree_rename_count(self):
1976
source = self.make_branch_and_tree('source')
1977
self.build_tree(['source/file1', 'source/dir1/'])
1978
source.add(['file1', 'dir1'])
1979
source.commit('add1')
1980
target1 = self.make_branch_and_tree('target1')
1981
transform_result = build_tree(source.basis_tree(), target1)
1982
self.assertEqual(2, transform_result.rename_count)
1984
self.build_tree(['source/dir1/file2'])
1985
source.add(['dir1/file2'])
1986
source.commit('add3')
1987
target2 = self.make_branch_and_tree('target2')
1988
transform_result = build_tree(source.basis_tree(), target2)
1989
# children of non-root directories should not be renamed
1990
self.assertEqual(2, transform_result.rename_count)
1992
def create_ab_tree(self):
1993
"""Create a committed test tree with two files"""
1994
source = self.make_branch_and_tree('source')
1995
self.build_tree_contents([('source/file1', 'A')])
1996
self.build_tree_contents([('source/file2', 'B')])
1997
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1998
source.commit('commit files')
2000
self.addCleanup(source.unlock)
2003
def test_build_tree_accelerator_tree(self):
2004
source = self.create_ab_tree()
2005
self.build_tree_contents([('source/file2', 'C')])
2007
real_source_get_file = source.get_file
2008
def get_file(file_id, path=None):
2009
calls.append(file_id)
2010
return real_source_get_file(file_id, path)
2011
source.get_file = get_file
2012
target = self.make_branch_and_tree('target')
2013
revision_tree = source.basis_tree()
2014
revision_tree.lock_read()
2015
self.addCleanup(revision_tree.unlock)
2016
build_tree(revision_tree, target, source)
2017
self.assertEqual(['file1-id'], calls)
2019
self.addCleanup(target.unlock)
2020
self.assertEqual([], list(target.iter_changes(revision_tree)))
2022
def test_build_tree_accelerator_tree_observes_sha1(self):
2023
source = self.create_ab_tree()
2024
sha1 = osutils.sha_string('A')
2025
target = self.make_branch_and_tree('target')
2027
self.addCleanup(target.unlock)
2028
state = target.current_dirstate()
2029
state._cutoff_time = time.time() + 60
2030
build_tree(source.basis_tree(), target, source)
2031
entry = state._get_entry(0, path_utf8='file1')
2032
self.assertEqual(sha1, entry[1][0][1])
2034
def test_build_tree_accelerator_tree_missing_file(self):
2035
source = self.create_ab_tree()
2036
os.unlink('source/file1')
2037
source.remove(['file2'])
2038
target = self.make_branch_and_tree('target')
2039
revision_tree = source.basis_tree()
2040
revision_tree.lock_read()
2041
self.addCleanup(revision_tree.unlock)
2042
build_tree(revision_tree, target, source)
2044
self.addCleanup(target.unlock)
2045
self.assertEqual([], list(target.iter_changes(revision_tree)))
2047
def test_build_tree_accelerator_wrong_kind(self):
2048
self.requireFeature(SymlinkFeature)
2049
source = self.make_branch_and_tree('source')
2050
self.build_tree_contents([('source/file1', '')])
2051
self.build_tree_contents([('source/file2', '')])
2052
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2053
source.commit('commit files')
2054
os.unlink('source/file2')
2055
self.build_tree_contents([('source/file2/', 'C')])
2056
os.unlink('source/file1')
2057
os.symlink('file2', 'source/file1')
2059
real_source_get_file = source.get_file
2060
def get_file(file_id, path=None):
2061
calls.append(file_id)
2062
return real_source_get_file(file_id, path)
2063
source.get_file = get_file
2064
target = self.make_branch_and_tree('target')
2065
revision_tree = source.basis_tree()
2066
revision_tree.lock_read()
2067
self.addCleanup(revision_tree.unlock)
2068
build_tree(revision_tree, target, source)
2069
self.assertEqual([], calls)
2071
self.addCleanup(target.unlock)
2072
self.assertEqual([], list(target.iter_changes(revision_tree)))
2074
def test_build_tree_hardlink(self):
2075
self.requireFeature(HardlinkFeature)
2076
source = self.create_ab_tree()
2077
target = self.make_branch_and_tree('target')
2078
revision_tree = source.basis_tree()
2079
revision_tree.lock_read()
2080
self.addCleanup(revision_tree.unlock)
2081
build_tree(revision_tree, target, source, hardlink=True)
2083
self.addCleanup(target.unlock)
2084
self.assertEqual([], list(target.iter_changes(revision_tree)))
2085
source_stat = os.stat('source/file1')
2086
target_stat = os.stat('target/file1')
2087
self.assertEqual(source_stat, target_stat)
2089
# Explicitly disallowing hardlinks should prevent them.
2090
target2 = self.make_branch_and_tree('target2')
2091
build_tree(revision_tree, target2, source, hardlink=False)
2093
self.addCleanup(target2.unlock)
2094
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2095
source_stat = os.stat('source/file1')
2096
target2_stat = os.stat('target2/file1')
2097
self.assertNotEqual(source_stat, target2_stat)
2099
def test_build_tree_accelerator_tree_moved(self):
2100
source = self.make_branch_and_tree('source')
2101
self.build_tree_contents([('source/file1', 'A')])
2102
source.add(['file1'], ['file1-id'])
2103
source.commit('commit files')
2104
source.rename_one('file1', 'file2')
2106
self.addCleanup(source.unlock)
2107
target = self.make_branch_and_tree('target')
2108
revision_tree = source.basis_tree()
2109
revision_tree.lock_read()
2110
self.addCleanup(revision_tree.unlock)
2111
build_tree(revision_tree, target, source)
2113
self.addCleanup(target.unlock)
2114
self.assertEqual([], list(target.iter_changes(revision_tree)))
2116
def test_build_tree_hardlinks_preserve_execute(self):
2117
self.requireFeature(HardlinkFeature)
2118
source = self.create_ab_tree()
2119
tt = TreeTransform(source)
2120
trans_id = tt.trans_id_tree_file_id('file1-id')
2121
tt.set_executability(True, trans_id)
2123
self.assertTrue(source.is_executable('file1-id'))
2124
target = self.make_branch_and_tree('target')
2125
revision_tree = source.basis_tree()
2126
revision_tree.lock_read()
2127
self.addCleanup(revision_tree.unlock)
2128
build_tree(revision_tree, target, source, hardlink=True)
2130
self.addCleanup(target.unlock)
2131
self.assertEqual([], list(target.iter_changes(revision_tree)))
2132
self.assertTrue(source.is_executable('file1-id'))
2134
def install_rot13_content_filter(self, pattern):
2136
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2137
# below, but that looks a bit... hard to read even if it's exactly
2139
original_registry = filters._reset_registry()
2140
def restore_registry():
2141
filters._reset_registry(original_registry)
2142
self.addCleanup(restore_registry)
2143
def rot13(chunks, context=None):
2144
return [''.join(chunks).encode('rot13')]
2145
rot13filter = filters.ContentFilter(rot13, rot13)
2146
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
2147
os.mkdir(self.test_home_dir + '/.bazaar')
2148
rules_filename = self.test_home_dir + '/.bazaar/rules'
2149
f = open(rules_filename, 'wb')
2150
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2152
def uninstall_rules():
2153
os.remove(rules_filename)
2155
self.addCleanup(uninstall_rules)
2158
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2159
"""build_tree will not hardlink files that have content filtering rules
2160
applied to them (but will still hardlink other files from the same tree
2163
self.requireFeature(HardlinkFeature)
2164
self.install_rot13_content_filter('file1')
2165
source = self.create_ab_tree()
2166
target = self.make_branch_and_tree('target')
2167
revision_tree = source.basis_tree()
2168
revision_tree.lock_read()
2169
self.addCleanup(revision_tree.unlock)
2170
build_tree(revision_tree, target, source, hardlink=True)
2172
self.addCleanup(target.unlock)
2173
self.assertEqual([], list(target.iter_changes(revision_tree)))
2174
source_stat = os.stat('source/file1')
2175
target_stat = os.stat('target/file1')
2176
self.assertNotEqual(source_stat, target_stat)
2177
source_stat = os.stat('source/file2')
2178
target_stat = os.stat('target/file2')
2179
self.assertEqualStat(source_stat, target_stat)
2181
def test_case_insensitive_build_tree_inventory(self):
2182
if (tests.CaseInsensitiveFilesystemFeature.available()
2183
or tests.CaseInsCasePresFilenameFeature.available()):
2184
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2185
source = self.make_branch_and_tree('source')
2186
self.build_tree(['source/file', 'source/FILE'])
2187
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2188
source.commit('added files')
2189
# Don't try this at home, kids!
2190
# Force the tree to report that it is case insensitive
2191
target = self.make_branch_and_tree('target')
2192
target.case_sensitive = False
2193
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2194
self.assertEqual('file.moved', target.id2path('lower-id'))
2195
self.assertEqual('FILE', target.id2path('upper-id'))
2197
def test_build_tree_observes_sha(self):
2198
source = self.make_branch_and_tree('source')
2199
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2200
source.add(['file1', 'dir', 'dir/file2'],
2201
['file1-id', 'dir-id', 'file2-id'])
2202
source.commit('new files')
2203
target = self.make_branch_and_tree('target')
2205
self.addCleanup(target.unlock)
2206
# We make use of the fact that DirState caches its cutoff time. So we
2207
# set the 'safe' time to one minute in the future.
2208
state = target.current_dirstate()
2209
state._cutoff_time = time.time() + 60
2210
build_tree(source.basis_tree(), target)
2211
entry1_sha = osutils.sha_file_by_name('source/file1')
2212
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2213
# entry[1] is the state information, entry[1][0] is the state of the
2214
# working tree, entry[1][0][1] is the sha value for the current working
2216
entry1 = state._get_entry(0, path_utf8='file1')
2217
self.assertEqual(entry1_sha, entry1[1][0][1])
2218
# The 'size' field must also be set.
2219
self.assertEqual(25, entry1[1][0][2])
2220
entry1_state = entry1[1][0]
2221
entry2 = state._get_entry(0, path_utf8='dir/file2')
2222
self.assertEqual(entry2_sha, entry2[1][0][1])
2223
self.assertEqual(29, entry2[1][0][2])
2224
entry2_state = entry2[1][0]
2225
# Now, make sure that we don't have to re-read the content. The
2226
# packed_stat should match exactly.
2227
self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
2228
self.assertEqual(entry2_sha,
2229
target.get_file_sha1('file2-id', 'dir/file2'))
2230
self.assertEqual(entry1_state, entry1[1][0])
2231
self.assertEqual(entry2_state, entry2[1][0])
2234
class TestCommitTransform(tests.TestCaseWithTransport):
2236
def get_branch(self):
2237
tree = self.make_branch_and_tree('tree')
2239
self.addCleanup(tree.unlock)
2240
tree.commit('empty commit')
2243
def get_branch_and_transform(self):
2244
branch = self.get_branch()
2245
tt = TransformPreview(branch.basis_tree())
2246
self.addCleanup(tt.finalize)
2249
def test_commit_wrong_basis(self):
2250
branch = self.get_branch()
2251
basis = branch.repository.revision_tree(
2252
_mod_revision.NULL_REVISION)
2253
tt = TransformPreview(basis)
2254
self.addCleanup(tt.finalize)
2255
e = self.assertRaises(ValueError, tt.commit, branch, '')
2256
self.assertEqual('TreeTransform not based on branch basis: null:',
2259
def test_empy_commit(self):
2260
branch, tt = self.get_branch_and_transform()
2261
rev = tt.commit(branch, 'my message')
2262
self.assertEqual(2, branch.revno())
2263
repo = branch.repository
2264
self.assertEqual('my message', repo.get_revision(rev).message)
2266
def test_merge_parents(self):
2267
branch, tt = self.get_branch_and_transform()
2268
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2269
self.assertEqual(['rev1b', 'rev1c'],
2270
branch.basis_tree().get_parent_ids()[1:])
2272
def test_first_commit(self):
2273
branch = self.make_branch('branch')
2275
self.addCleanup(branch.unlock)
2276
tt = TransformPreview(branch.basis_tree())
2277
self.addCleanup(tt.finalize)
2278
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2279
rev = tt.commit(branch, 'my message')
2280
self.assertEqual([], branch.basis_tree().get_parent_ids())
2281
self.assertNotEqual(_mod_revision.NULL_REVISION,
2282
branch.last_revision())
2284
def test_first_commit_with_merge_parents(self):
2285
branch = self.make_branch('branch')
2287
self.addCleanup(branch.unlock)
2288
tt = TransformPreview(branch.basis_tree())
2289
self.addCleanup(tt.finalize)
2290
e = self.assertRaises(ValueError, tt.commit, branch,
2291
'my message', ['rev1b-id'])
2292
self.assertEqual('Cannot supply merge parents for first commit.',
2294
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2296
def test_add_files(self):
2297
branch, tt = self.get_branch_and_transform()
2298
tt.new_file('file', tt.root, 'contents', 'file-id')
2299
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2300
if SymlinkFeature.available():
2301
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2302
rev = tt.commit(branch, 'message')
2303
tree = branch.basis_tree()
2304
self.assertEqual('file', tree.id2path('file-id'))
2305
self.assertEqual('contents', tree.get_file_text('file-id'))
2306
self.assertEqual('dir', tree.id2path('dir-id'))
2307
if SymlinkFeature.available():
2308
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2309
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2311
def test_add_unversioned(self):
2312
branch, tt = self.get_branch_and_transform()
2313
tt.new_file('file', tt.root, 'contents')
2314
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2315
'message', strict=True)
2317
def test_modify_strict(self):
2318
branch, tt = self.get_branch_and_transform()
2319
tt.new_file('file', tt.root, 'contents', 'file-id')
2320
tt.commit(branch, 'message', strict=True)
2321
tt = TransformPreview(branch.basis_tree())
2322
self.addCleanup(tt.finalize)
2323
trans_id = tt.trans_id_file_id('file-id')
2324
tt.delete_contents(trans_id)
2325
tt.create_file('contents', trans_id)
2326
tt.commit(branch, 'message', strict=True)
2328
def test_commit_malformed(self):
2329
"""Committing a malformed transform should raise an exception.
2331
In this case, we are adding a file without adding its parent.
2333
branch, tt = self.get_branch_and_transform()
2334
parent_id = tt.trans_id_file_id('parent-id')
2335
tt.new_file('file', parent_id, 'contents', 'file-id')
2336
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2339
def test_commit_rich_revision_data(self):
2340
branch, tt = self.get_branch_and_transform()
2341
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2342
committer='me <me@example.com>',
2343
revprops={'foo': 'bar'}, revision_id='revid-1',
2344
authors=['Author1 <author1@example.com>',
2345
'Author2 <author2@example.com>',
2347
self.assertEqual('revid-1', rev_id)
2348
revision = branch.repository.get_revision(rev_id)
2349
self.assertEqual(1, revision.timestamp)
2350
self.assertEqual(43201, revision.timezone)
2351
self.assertEqual('me <me@example.com>', revision.committer)
2352
self.assertEqual(['Author1 <author1@example.com>',
2353
'Author2 <author2@example.com>'],
2354
revision.get_apparent_authors())
2355
del revision.properties['authors']
2356
self.assertEqual({'foo': 'bar',
2357
'branch-nick': 'tree'},
2358
revision.properties)
2360
def test_no_explicit_revprops(self):
2361
branch, tt = self.get_branch_and_transform()
2362
rev_id = tt.commit(branch, 'message', authors=[
2363
'Author1 <author1@example.com>',
2364
'Author2 <author2@example.com>', ])
2365
revision = branch.repository.get_revision(rev_id)
2366
self.assertEqual(['Author1 <author1@example.com>',
2367
'Author2 <author2@example.com>'],
2368
revision.get_apparent_authors())
2369
self.assertEqual('tree', revision.properties['branch-nick'])
2372
class TestBackupName(tests.TestCase):
2374
def test_deprecations(self):
2375
class MockTransform(object):
2377
def has_named_child(self, by_parent, parent_id, name):
2378
return name in by_parent.get(parent_id, [])
2380
class MockEntry(object):
2383
object.__init__(self)
724
2386
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~')
2387
name1 = self.applyDeprecated(
2388
symbol_versioning.deprecated_in((2, 3, 0)),
2389
transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
2390
self.assertEqual('name.~1~', name1)
2391
name2 = self.applyDeprecated(
2392
symbol_versioning.deprecated_in((2, 3, 0)),
2393
transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
2394
self.assertEqual('name.~2~', name2)
2397
class TestFileMover(tests.TestCaseWithTransport):
2399
def test_file_mover(self):
2400
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2401
mover = _FileMover()
2402
mover.rename('a', 'q')
2403
self.assertPathExists('q')
2404
self.assertPathDoesNotExist('a')
2405
self.assertPathExists('q/b')
2406
self.assertPathExists('c')
2407
self.assertPathExists('c/d')
2409
def test_pre_delete_rollback(self):
2410
self.build_tree(['a/'])
2411
mover = _FileMover()
2412
mover.pre_delete('a', 'q')
2413
self.assertPathExists('q')
2414
self.assertPathDoesNotExist('a')
2416
self.assertPathDoesNotExist('q')
2417
self.assertPathExists('a')
2419
def test_apply_deletions(self):
2420
self.build_tree(['a/', 'b/'])
2421
mover = _FileMover()
2422
mover.pre_delete('a', 'q')
2423
mover.pre_delete('b', 'r')
2424
self.assertPathExists('q')
2425
self.assertPathExists('r')
2426
self.assertPathDoesNotExist('a')
2427
self.assertPathDoesNotExist('b')
2428
mover.apply_deletions()
2429
self.assertPathDoesNotExist('q')
2430
self.assertPathDoesNotExist('r')
2431
self.assertPathDoesNotExist('a')
2432
self.assertPathDoesNotExist('b')
2434
def test_file_mover_rollback(self):
2435
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2436
mover = _FileMover()
2437
mover.rename('c/d', 'c/f')
2438
mover.rename('c/e', 'c/d')
2440
mover.rename('a', 'c')
2441
except errors.FileExists, e:
2443
self.assertPathExists('a')
2444
self.assertPathExists('c/d')
2447
class Bogus(Exception):
2451
class TestTransformRollback(tests.TestCaseWithTransport):
2453
class ExceptionFileMover(_FileMover):
2455
def __init__(self, bad_source=None, bad_target=None):
2456
_FileMover.__init__(self)
2457
self.bad_source = bad_source
2458
self.bad_target = bad_target
2460
def rename(self, source, target):
2461
if (self.bad_source is not None and
2462
source.endswith(self.bad_source)):
2464
elif (self.bad_target is not None and
2465
target.endswith(self.bad_target)):
2468
_FileMover.rename(self, source, target)
2470
def test_rollback_rename(self):
2471
tree = self.make_branch_and_tree('.')
2472
self.build_tree(['a/', 'a/b'])
2473
tt = TreeTransform(tree)
2474
self.addCleanup(tt.finalize)
2475
a_id = tt.trans_id_tree_path('a')
2476
tt.adjust_path('c', tt.root, a_id)
2477
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2478
self.assertRaises(Bogus, tt.apply,
2479
_mover=self.ExceptionFileMover(bad_source='a'))
2480
self.assertPathExists('a')
2481
self.assertPathExists('a/b')
2483
self.assertPathExists('c')
2484
self.assertPathExists('c/d')
2486
def test_rollback_rename_into_place(self):
2487
tree = self.make_branch_and_tree('.')
2488
self.build_tree(['a/', 'a/b'])
2489
tt = TreeTransform(tree)
2490
self.addCleanup(tt.finalize)
2491
a_id = tt.trans_id_tree_path('a')
2492
tt.adjust_path('c', tt.root, a_id)
2493
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2494
self.assertRaises(Bogus, tt.apply,
2495
_mover=self.ExceptionFileMover(bad_target='c/d'))
2496
self.assertPathExists('a')
2497
self.assertPathExists('a/b')
2499
self.assertPathExists('c')
2500
self.assertPathExists('c/d')
2502
def test_rollback_deletion(self):
2503
tree = self.make_branch_and_tree('.')
2504
self.build_tree(['a/', 'a/b'])
2505
tt = TreeTransform(tree)
2506
self.addCleanup(tt.finalize)
2507
a_id = tt.trans_id_tree_path('a')
2508
tt.delete_contents(a_id)
2509
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2510
self.assertRaises(Bogus, tt.apply,
2511
_mover=self.ExceptionFileMover(bad_target='d'))
2512
self.assertPathExists('a')
2513
self.assertPathExists('a/b')
2516
class TestFinalizeRobustness(tests.TestCaseWithTransport):
2517
"""Ensure treetransform creation errors can be safely cleaned up after"""
2519
def _override_globals_in_method(self, instance, method_name, globals):
2520
"""Replace method on instance with one with updated globals"""
2522
func = getattr(instance, method_name).im_func
2523
new_globals = dict(func.func_globals)
2524
new_globals.update(globals)
2525
new_func = types.FunctionType(func.func_code, new_globals,
2526
func.func_name, func.func_defaults)
2527
setattr(instance, method_name,
2528
types.MethodType(new_func, instance, instance.__class__))
2529
self.addCleanup(delattr, instance, method_name)
2532
def _fake_open_raises_before(name, mode):
2533
"""Like open() but raises before doing anything"""
2537
def _fake_open_raises_after(name, mode):
2538
"""Like open() but raises after creating file without returning"""
2539
open(name, mode).close()
2542
def create_transform_and_root_trans_id(self):
2543
"""Setup a transform creating a file in limbo"""
2544
tree = self.make_branch_and_tree('.')
2545
tt = TreeTransform(tree)
2546
return tt, tt.create_path("a", tt.root)
2548
def create_transform_and_subdir_trans_id(self):
2549
"""Setup a transform creating a directory containing a file in limbo"""
2550
tree = self.make_branch_and_tree('.')
2551
tt = TreeTransform(tree)
2552
d_trans_id = tt.create_path("d", tt.root)
2553
tt.create_directory(d_trans_id)
2554
f_trans_id = tt.create_path("a", d_trans_id)
2555
tt.adjust_path("a", d_trans_id, f_trans_id)
2556
return tt, f_trans_id
2558
def test_root_create_file_open_raises_before_creation(self):
2559
tt, trans_id = self.create_transform_and_root_trans_id()
2560
self._override_globals_in_method(tt, "create_file",
2561
{"open": self._fake_open_raises_before})
2562
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2563
path = tt._limbo_name(trans_id)
2564
self.assertPathDoesNotExist(path)
2566
self.assertPathDoesNotExist(tt._limbodir)
2568
def test_root_create_file_open_raises_after_creation(self):
2569
tt, trans_id = self.create_transform_and_root_trans_id()
2570
self._override_globals_in_method(tt, "create_file",
2571
{"open": self._fake_open_raises_after})
2572
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2573
path = tt._limbo_name(trans_id)
2574
self.assertPathExists(path)
2576
self.assertPathDoesNotExist(path)
2577
self.assertPathDoesNotExist(tt._limbodir)
2579
def test_subdir_create_file_open_raises_before_creation(self):
2580
tt, trans_id = self.create_transform_and_subdir_trans_id()
2581
self._override_globals_in_method(tt, "create_file",
2582
{"open": self._fake_open_raises_before})
2583
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2584
path = tt._limbo_name(trans_id)
2585
self.assertPathDoesNotExist(path)
2587
self.assertPathDoesNotExist(tt._limbodir)
2589
def test_subdir_create_file_open_raises_after_creation(self):
2590
tt, trans_id = self.create_transform_and_subdir_trans_id()
2591
self._override_globals_in_method(tt, "create_file",
2592
{"open": self._fake_open_raises_after})
2593
self.assertRaises(RuntimeError, tt.create_file, ["contents"], trans_id)
2594
path = tt._limbo_name(trans_id)
2595
self.assertPathExists(path)
2597
self.assertPathDoesNotExist(path)
2598
self.assertPathDoesNotExist(tt._limbodir)
2600
def test_rename_in_limbo_rename_raises_after_rename(self):
2601
tt, trans_id = self.create_transform_and_root_trans_id()
2602
parent1 = tt.new_directory('parent1', tt.root)
2603
child1 = tt.new_file('child1', parent1, 'contents')
2604
parent2 = tt.new_directory('parent2', tt.root)
2606
class FakeOSModule(object):
2607
def rename(self, old, new):
2610
self._override_globals_in_method(tt, "_rename_in_limbo",
2611
{"os": FakeOSModule()})
2613
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2614
path = osutils.pathjoin(tt._limbo_name(parent2), "child1")
2615
self.assertPathExists(path)
2617
self.assertPathDoesNotExist(path)
2618
self.assertPathDoesNotExist(tt._limbodir)
2620
def test_rename_in_limbo_rename_raises_before_rename(self):
2621
tt, trans_id = self.create_transform_and_root_trans_id()
2622
parent1 = tt.new_directory('parent1', tt.root)
2623
child1 = tt.new_file('child1', parent1, 'contents')
2624
parent2 = tt.new_directory('parent2', tt.root)
2626
class FakeOSModule(object):
2627
def rename(self, old, new):
2629
self._override_globals_in_method(tt, "_rename_in_limbo",
2630
{"os": FakeOSModule()})
2632
RuntimeError, tt.adjust_path, "child1", parent2, child1)
2633
path = osutils.pathjoin(tt._limbo_name(parent1), "child1")
2634
self.assertPathExists(path)
2636
self.assertPathDoesNotExist(path)
2637
self.assertPathDoesNotExist(tt._limbodir)
2640
class TestTransformMissingParent(tests.TestCaseWithTransport):
2642
def make_tt_with_versioned_dir(self):
2643
wt = self.make_branch_and_tree('.')
2644
self.build_tree(['dir/',])
2645
wt.add(['dir'], ['dir-id'])
2646
wt.commit('Create dir')
2647
tt = TreeTransform(wt)
2648
self.addCleanup(tt.finalize)
2651
def test_resolve_create_parent_for_versioned_file(self):
2652
wt, tt = self.make_tt_with_versioned_dir()
2653
dir_tid = tt.trans_id_tree_file_id('dir-id')
2654
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2655
tt.delete_contents(dir_tid)
2656
tt.unversion_file(dir_tid)
2657
conflicts = resolve_conflicts(tt)
2658
# one conflict for the missing directory, one for the unversioned
2660
self.assertLength(2, conflicts)
2662
def test_non_versioned_file_create_conflict(self):
2663
wt, tt = self.make_tt_with_versioned_dir()
2664
dir_tid = tt.trans_id_tree_file_id('dir-id')
2665
tt.new_file('file', dir_tid, 'Contents')
2666
tt.delete_contents(dir_tid)
2667
tt.unversion_file(dir_tid)
2668
conflicts = resolve_conflicts(tt)
2669
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2670
self.assertLength(1, conflicts)
2671
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2675
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2676
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2678
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2679
('', ''), ('directory', 'directory'), (False, False))
2682
class TestTransformPreview(tests.TestCaseWithTransport):
2684
def create_tree(self):
2685
tree = self.make_branch_and_tree('.')
2686
self.build_tree_contents([('a', 'content 1')])
2687
tree.set_root_id('TREE_ROOT')
2688
tree.add('a', 'a-id')
2689
tree.commit('rev1', rev_id='rev1')
2690
return tree.branch.repository.revision_tree('rev1')
2692
def get_empty_preview(self):
2693
repository = self.make_repository('repo')
2694
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2695
preview = TransformPreview(tree)
2696
self.addCleanup(preview.finalize)
2699
def test_transform_preview(self):
2700
revision_tree = self.create_tree()
2701
preview = TransformPreview(revision_tree)
2702
self.addCleanup(preview.finalize)
2704
def test_transform_preview_tree(self):
2705
revision_tree = self.create_tree()
2706
preview = TransformPreview(revision_tree)
2707
self.addCleanup(preview.finalize)
2708
preview.get_preview_tree()
2710
def test_transform_new_file(self):
2711
revision_tree = self.create_tree()
2712
preview = TransformPreview(revision_tree)
2713
self.addCleanup(preview.finalize)
2714
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2715
preview_tree = preview.get_preview_tree()
2716
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2718
preview_tree.get_file('file2-id').read(), 'content B\n')
2720
def test_diff_preview_tree(self):
2721
revision_tree = self.create_tree()
2722
preview = TransformPreview(revision_tree)
2723
self.addCleanup(preview.finalize)
2724
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2725
preview_tree = preview.get_preview_tree()
2727
show_diff_trees(revision_tree, preview_tree, out)
2728
lines = out.getvalue().splitlines()
2729
self.assertEqual(lines[0], "=== added file 'file2'")
2730
# 3 lines of diff administrivia
2731
self.assertEqual(lines[4], "+content B")
2733
def test_transform_conflicts(self):
2734
revision_tree = self.create_tree()
2735
preview = TransformPreview(revision_tree)
2736
self.addCleanup(preview.finalize)
2737
preview.new_file('a', preview.root, 'content 2')
2738
resolve_conflicts(preview)
2739
trans_id = preview.trans_id_file_id('a-id')
2740
self.assertEqual('a.moved', preview.final_name(trans_id))
2742
def get_tree_and_preview_tree(self):
2743
revision_tree = self.create_tree()
2744
preview = TransformPreview(revision_tree)
2745
self.addCleanup(preview.finalize)
2746
a_trans_id = preview.trans_id_file_id('a-id')
2747
preview.delete_contents(a_trans_id)
2748
preview.create_file('b content', a_trans_id)
2749
preview_tree = preview.get_preview_tree()
2750
return revision_tree, preview_tree
2752
def test_iter_changes(self):
2753
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2754
root = revision_tree.inventory.root.file_id
2755
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2756
(root, root), ('a', 'a'), ('file', 'file'),
2758
list(preview_tree.iter_changes(revision_tree)))
2760
def test_include_unchanged_succeeds(self):
2761
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2762
changes = preview_tree.iter_changes(revision_tree,
2763
include_unchanged=True)
2764
root = revision_tree.inventory.root.file_id
2766
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2768
def test_specific_files(self):
2769
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2770
changes = preview_tree.iter_changes(revision_tree,
2771
specific_files=[''])
2772
self.assertEqual([A_ENTRY], list(changes))
2774
def test_want_unversioned(self):
2775
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2776
changes = preview_tree.iter_changes(revision_tree,
2777
want_unversioned=True)
2778
self.assertEqual([A_ENTRY], list(changes))
2780
def test_ignore_extra_trees_no_specific_files(self):
2781
# extra_trees is harmless without specific_files, so we'll silently
2782
# accept it, even though we won't use it.
2783
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2784
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2786
def test_ignore_require_versioned_no_specific_files(self):
2787
# require_versioned is meaningless without specific_files.
2788
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2789
preview_tree.iter_changes(revision_tree, require_versioned=False)
2791
def test_ignore_pb(self):
2792
# pb could be supported, but TT.iter_changes doesn't support it.
2793
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2794
preview_tree.iter_changes(revision_tree)
2796
def test_kind(self):
2797
revision_tree = self.create_tree()
2798
preview = TransformPreview(revision_tree)
2799
self.addCleanup(preview.finalize)
2800
preview.new_file('file', preview.root, 'contents', 'file-id')
2801
preview.new_directory('directory', preview.root, 'dir-id')
2802
preview_tree = preview.get_preview_tree()
2803
self.assertEqual('file', preview_tree.kind('file-id'))
2804
self.assertEqual('directory', preview_tree.kind('dir-id'))
2806
def test_get_file_mtime(self):
2807
preview = self.get_empty_preview()
2808
file_trans_id = preview.new_file('file', preview.root, 'contents',
2810
limbo_path = preview._limbo_name(file_trans_id)
2811
preview_tree = preview.get_preview_tree()
2812
self.assertEqual(os.stat(limbo_path).st_mtime,
2813
preview_tree.get_file_mtime('file-id'))
2815
def test_get_file_mtime_renamed(self):
2816
work_tree = self.make_branch_and_tree('tree')
2817
self.build_tree(['tree/file'])
2818
work_tree.add('file', 'file-id')
2819
preview = TransformPreview(work_tree)
2820
self.addCleanup(preview.finalize)
2821
file_trans_id = preview.trans_id_tree_file_id('file-id')
2822
preview.adjust_path('renamed', preview.root, file_trans_id)
2823
preview_tree = preview.get_preview_tree()
2824
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2825
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2827
def test_get_file_size(self):
2828
work_tree = self.make_branch_and_tree('tree')
2829
self.build_tree_contents([('tree/old', 'old')])
2830
work_tree.add('old', 'old-id')
2831
preview = TransformPreview(work_tree)
2832
self.addCleanup(preview.finalize)
2833
new_id = preview.new_file('name', preview.root, 'contents', 'new-id',
2835
tree = preview.get_preview_tree()
2836
self.assertEqual(len('old'), tree.get_file_size('old-id'))
2837
self.assertEqual(len('contents'), tree.get_file_size('new-id'))
2839
def test_get_file(self):
2840
preview = self.get_empty_preview()
2841
preview.new_file('file', preview.root, 'contents', 'file-id')
2842
preview_tree = preview.get_preview_tree()
2843
tree_file = preview_tree.get_file('file-id')
2845
self.assertEqual('contents', tree_file.read())
2849
def test_get_symlink_target(self):
2850
self.requireFeature(SymlinkFeature)
2851
preview = self.get_empty_preview()
2852
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2853
preview_tree = preview.get_preview_tree()
2854
self.assertEqual('target',
2855
preview_tree.get_symlink_target('symlink-id'))
2857
def test_all_file_ids(self):
2858
tree = self.make_branch_and_tree('tree')
2859
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2860
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2861
preview = TransformPreview(tree)
2862
self.addCleanup(preview.finalize)
2863
preview.unversion_file(preview.trans_id_file_id('b-id'))
2864
c_trans_id = preview.trans_id_file_id('c-id')
2865
preview.unversion_file(c_trans_id)
2866
preview.version_file('c-id', c_trans_id)
2867
preview_tree = preview.get_preview_tree()
2868
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2869
preview_tree.all_file_ids())
2871
def test_path2id_deleted_unchanged(self):
2872
tree = self.make_branch_and_tree('tree')
2873
self.build_tree(['tree/unchanged', 'tree/deleted'])
2874
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2875
preview = TransformPreview(tree)
2876
self.addCleanup(preview.finalize)
2877
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2878
preview_tree = preview.get_preview_tree()
2879
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2880
self.assertIs(None, preview_tree.path2id('deleted'))
2882
def test_path2id_created(self):
2883
tree = self.make_branch_and_tree('tree')
2884
self.build_tree(['tree/unchanged'])
2885
tree.add(['unchanged'], ['unchanged-id'])
2886
preview = TransformPreview(tree)
2887
self.addCleanup(preview.finalize)
2888
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2889
'contents', 'new-id')
2890
preview_tree = preview.get_preview_tree()
2891
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2893
def test_path2id_moved(self):
2894
tree = self.make_branch_and_tree('tree')
2895
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2896
tree.add(['old_parent', 'old_parent/child'],
2897
['old_parent-id', 'child-id'])
2898
preview = TransformPreview(tree)
2899
self.addCleanup(preview.finalize)
2900
new_parent = preview.new_directory('new_parent', preview.root,
2902
preview.adjust_path('child', new_parent,
2903
preview.trans_id_file_id('child-id'))
2904
preview_tree = preview.get_preview_tree()
2905
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2906
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2908
def test_path2id_renamed_parent(self):
2909
tree = self.make_branch_and_tree('tree')
2910
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2911
tree.add(['old_name', 'old_name/child'],
2912
['parent-id', 'child-id'])
2913
preview = TransformPreview(tree)
2914
self.addCleanup(preview.finalize)
2915
preview.adjust_path('new_name', preview.root,
2916
preview.trans_id_file_id('parent-id'))
2917
preview_tree = preview.get_preview_tree()
2918
self.assertIs(None, preview_tree.path2id('old_name/child'))
2919
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2921
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2922
preview_tree = tt.get_preview_tree()
2923
preview_result = list(preview_tree.iter_entries_by_dir(
2927
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2928
self.assertEqual(actual_result, preview_result)
2930
def test_iter_entries_by_dir_new(self):
2931
tree = self.make_branch_and_tree('tree')
2932
tt = TreeTransform(tree)
2933
tt.new_file('new', tt.root, 'contents', 'new-id')
2934
self.assertMatchingIterEntries(tt)
2936
def test_iter_entries_by_dir_deleted(self):
2937
tree = self.make_branch_and_tree('tree')
2938
self.build_tree(['tree/deleted'])
2939
tree.add('deleted', 'deleted-id')
2940
tt = TreeTransform(tree)
2941
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2942
self.assertMatchingIterEntries(tt)
2944
def test_iter_entries_by_dir_unversioned(self):
2945
tree = self.make_branch_and_tree('tree')
2946
self.build_tree(['tree/removed'])
2947
tree.add('removed', 'removed-id')
2948
tt = TreeTransform(tree)
2949
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2950
self.assertMatchingIterEntries(tt)
2952
def test_iter_entries_by_dir_moved(self):
2953
tree = self.make_branch_and_tree('tree')
2954
self.build_tree(['tree/moved', 'tree/new_parent/'])
2955
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2956
tt = TreeTransform(tree)
2957
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2958
tt.trans_id_file_id('moved-id'))
2959
self.assertMatchingIterEntries(tt)
2961
def test_iter_entries_by_dir_specific_file_ids(self):
2962
tree = self.make_branch_and_tree('tree')
2963
tree.set_root_id('tree-root-id')
2964
self.build_tree(['tree/parent/', 'tree/parent/child'])
2965
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2966
tt = TreeTransform(tree)
2967
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2969
def test_symlink_content_summary(self):
2970
self.requireFeature(SymlinkFeature)
2971
preview = self.get_empty_preview()
2972
preview.new_symlink('path', preview.root, 'target', 'path-id')
2973
summary = preview.get_preview_tree().path_content_summary('path')
2974
self.assertEqual(('symlink', None, None, 'target'), summary)
2976
def test_missing_content_summary(self):
2977
preview = self.get_empty_preview()
2978
summary = preview.get_preview_tree().path_content_summary('path')
2979
self.assertEqual(('missing', None, None, None), summary)
2981
def test_deleted_content_summary(self):
2982
tree = self.make_branch_and_tree('tree')
2983
self.build_tree(['tree/path/'])
2985
preview = TransformPreview(tree)
2986
self.addCleanup(preview.finalize)
2987
preview.delete_contents(preview.trans_id_tree_path('path'))
2988
summary = preview.get_preview_tree().path_content_summary('path')
2989
self.assertEqual(('missing', None, None, None), summary)
2991
def test_file_content_summary_executable(self):
2992
preview = self.get_empty_preview()
2993
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2994
preview.set_executability(True, path_id)
2995
summary = preview.get_preview_tree().path_content_summary('path')
2996
self.assertEqual(4, len(summary))
2997
self.assertEqual('file', summary[0])
2998
# size must be known
2999
self.assertEqual(len('contents'), summary[1])
3001
self.assertEqual(True, summary[2])
3002
# will not have hash (not cheap to determine)
3003
self.assertIs(None, summary[3])
3005
def test_change_executability(self):
3006
tree = self.make_branch_and_tree('tree')
3007
self.build_tree(['tree/path'])
3009
preview = TransformPreview(tree)
3010
self.addCleanup(preview.finalize)
3011
path_id = preview.trans_id_tree_path('path')
3012
preview.set_executability(True, path_id)
3013
summary = preview.get_preview_tree().path_content_summary('path')
3014
self.assertEqual(True, summary[2])
3016
def test_file_content_summary_non_exec(self):
3017
preview = self.get_empty_preview()
3018
preview.new_file('path', preview.root, 'contents', 'path-id')
3019
summary = preview.get_preview_tree().path_content_summary('path')
3020
self.assertEqual(4, len(summary))
3021
self.assertEqual('file', summary[0])
3022
# size must be known
3023
self.assertEqual(len('contents'), summary[1])
3025
self.assertEqual(False, summary[2])
3026
# will not have hash (not cheap to determine)
3027
self.assertIs(None, summary[3])
3029
def test_dir_content_summary(self):
3030
preview = self.get_empty_preview()
3031
preview.new_directory('path', preview.root, 'path-id')
3032
summary = preview.get_preview_tree().path_content_summary('path')
3033
self.assertEqual(('directory', None, None, None), summary)
3035
def test_tree_content_summary(self):
3036
preview = self.get_empty_preview()
3037
path = preview.new_directory('path', preview.root, 'path-id')
3038
preview.set_tree_reference('rev-1', path)
3039
summary = preview.get_preview_tree().path_content_summary('path')
3040
self.assertEqual(4, len(summary))
3041
self.assertEqual('tree-reference', summary[0])
3043
def test_annotate(self):
3044
tree = self.make_branch_and_tree('tree')
3045
self.build_tree_contents([('tree/file', 'a\n')])
3046
tree.add('file', 'file-id')
3047
tree.commit('a', rev_id='one')
3048
self.build_tree_contents([('tree/file', 'a\nb\n')])
3049
preview = TransformPreview(tree)
3050
self.addCleanup(preview.finalize)
3051
file_trans_id = preview.trans_id_file_id('file-id')
3052
preview.delete_contents(file_trans_id)
3053
preview.create_file('a\nb\nc\n', file_trans_id)
3054
preview_tree = preview.get_preview_tree()
3060
annotation = preview_tree.annotate_iter('file-id', 'me:')
3061
self.assertEqual(expected, annotation)
3063
def test_annotate_missing(self):
3064
preview = self.get_empty_preview()
3065
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3066
preview_tree = preview.get_preview_tree()
3072
annotation = preview_tree.annotate_iter('file-id', 'me:')
3073
self.assertEqual(expected, annotation)
3075
def test_annotate_rename(self):
3076
tree = self.make_branch_and_tree('tree')
3077
self.build_tree_contents([('tree/file', 'a\n')])
3078
tree.add('file', 'file-id')
3079
tree.commit('a', rev_id='one')
3080
preview = TransformPreview(tree)
3081
self.addCleanup(preview.finalize)
3082
file_trans_id = preview.trans_id_file_id('file-id')
3083
preview.adjust_path('newname', preview.root, file_trans_id)
3084
preview_tree = preview.get_preview_tree()
3088
annotation = preview_tree.annotate_iter('file-id', 'me:')
3089
self.assertEqual(expected, annotation)
3091
def test_annotate_deleted(self):
3092
tree = self.make_branch_and_tree('tree')
3093
self.build_tree_contents([('tree/file', 'a\n')])
3094
tree.add('file', 'file-id')
3095
tree.commit('a', rev_id='one')
3096
self.build_tree_contents([('tree/file', 'a\nb\n')])
3097
preview = TransformPreview(tree)
3098
self.addCleanup(preview.finalize)
3099
file_trans_id = preview.trans_id_file_id('file-id')
3100
preview.delete_contents(file_trans_id)
3101
preview_tree = preview.get_preview_tree()
3102
annotation = preview_tree.annotate_iter('file-id', 'me:')
3103
self.assertIs(None, annotation)
3105
def test_stored_kind(self):
3106
preview = self.get_empty_preview()
3107
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3108
preview_tree = preview.get_preview_tree()
3109
self.assertEqual('file', preview_tree.stored_kind('file-id'))
3111
def test_is_executable(self):
3112
preview = self.get_empty_preview()
3113
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
3114
preview.set_executability(True, preview.trans_id_file_id('file-id'))
3115
preview_tree = preview.get_preview_tree()
3116
self.assertEqual(True, preview_tree.is_executable('file-id'))
3118
def test_get_set_parent_ids(self):
3119
revision_tree, preview_tree = self.get_tree_and_preview_tree()
3120
self.assertEqual([], preview_tree.get_parent_ids())
3121
preview_tree.set_parent_ids(['rev-1'])
3122
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
3124
def test_plan_file_merge(self):
3125
work_a = self.make_branch_and_tree('wta')
3126
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3127
work_a.add('file', 'file-id')
3128
base_id = work_a.commit('base version')
3129
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3130
preview = TransformPreview(work_a)
3131
self.addCleanup(preview.finalize)
3132
trans_id = preview.trans_id_file_id('file-id')
3133
preview.delete_contents(trans_id)
3134
preview.create_file('b\nc\nd\ne\n', trans_id)
3135
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3136
tree_a = preview.get_preview_tree()
3137
tree_a.set_parent_ids([base_id])
3139
('killed-a', 'a\n'),
3140
('killed-b', 'b\n'),
3141
('unchanged', 'c\n'),
3142
('unchanged', 'd\n'),
3145
], list(tree_a.plan_file_merge('file-id', tree_b)))
3147
def test_plan_file_merge_revision_tree(self):
3148
work_a = self.make_branch_and_tree('wta')
3149
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
3150
work_a.add('file', 'file-id')
3151
base_id = work_a.commit('base version')
3152
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
3153
preview = TransformPreview(work_a.basis_tree())
3154
self.addCleanup(preview.finalize)
3155
trans_id = preview.trans_id_file_id('file-id')
3156
preview.delete_contents(trans_id)
3157
preview.create_file('b\nc\nd\ne\n', trans_id)
3158
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
3159
tree_a = preview.get_preview_tree()
3160
tree_a.set_parent_ids([base_id])
3162
('killed-a', 'a\n'),
3163
('killed-b', 'b\n'),
3164
('unchanged', 'c\n'),
3165
('unchanged', 'd\n'),
3168
], list(tree_a.plan_file_merge('file-id', tree_b)))
3170
def test_walkdirs(self):
3171
preview = self.get_empty_preview()
3172
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
3173
# FIXME: new_directory should mark root.
3174
preview.fixup_new_roots()
3175
preview_tree = preview.get_preview_tree()
3176
file_trans_id = preview.new_file('a', preview.root, 'contents',
3178
expected = [(('', 'tree-root'),
3179
[('a', 'a', 'file', None, 'a-id', 'file')])]
3180
self.assertEqual(expected, list(preview_tree.walkdirs()))
3182
def test_extras(self):
3183
work_tree = self.make_branch_and_tree('tree')
3184
self.build_tree(['tree/removed-file', 'tree/existing-file',
3185
'tree/not-removed-file'])
3186
work_tree.add(['removed-file', 'not-removed-file'])
3187
preview = TransformPreview(work_tree)
3188
self.addCleanup(preview.finalize)
3189
preview.new_file('new-file', preview.root, 'contents')
3190
preview.new_file('new-versioned-file', preview.root, 'contents',
3192
tree = preview.get_preview_tree()
3193
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3194
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
3197
def test_merge_into_preview(self):
3198
work_tree = self.make_branch_and_tree('tree')
3199
self.build_tree_contents([('tree/file','b\n')])
3200
work_tree.add('file', 'file-id')
3201
work_tree.commit('first commit')
3202
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
3203
self.build_tree_contents([('child/file','b\nc\n')])
3204
child_tree.commit('child commit')
3205
child_tree.lock_write()
3206
self.addCleanup(child_tree.unlock)
3207
work_tree.lock_write()
3208
self.addCleanup(work_tree.unlock)
3209
preview = TransformPreview(work_tree)
3210
self.addCleanup(preview.finalize)
3211
file_trans_id = preview.trans_id_file_id('file-id')
3212
preview.delete_contents(file_trans_id)
3213
preview.create_file('a\nb\n', file_trans_id)
3214
preview_tree = preview.get_preview_tree()
3215
merger = Merger.from_revision_ids(None, preview_tree,
3216
child_tree.branch.last_revision(),
3217
other_branch=child_tree.branch,
3218
tree_branch=work_tree.branch)
3219
merger.merge_type = Merge3Merger
3220
tt = merger.make_merger().make_preview_transform()
3221
self.addCleanup(tt.finalize)
3222
final_tree = tt.get_preview_tree()
3223
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3225
def test_merge_preview_into_workingtree(self):
3226
tree = self.make_branch_and_tree('tree')
3227
tree.set_root_id('TREE_ROOT')
3228
tt = TransformPreview(tree)
3229
self.addCleanup(tt.finalize)
3230
tt.new_file('name', tt.root, 'content', 'file-id')
3231
tree2 = self.make_branch_and_tree('tree2')
3232
tree2.set_root_id('TREE_ROOT')
3233
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3234
None, tree.basis_tree())
3235
merger.merge_type = Merge3Merger
3238
def test_merge_preview_into_workingtree_handles_conflicts(self):
3239
tree = self.make_branch_and_tree('tree')
3240
self.build_tree_contents([('tree/foo', 'bar')])
3241
tree.add('foo', 'foo-id')
3243
tt = TransformPreview(tree)
3244
self.addCleanup(tt.finalize)
3245
trans_id = tt.trans_id_file_id('foo-id')
3246
tt.delete_contents(trans_id)
3247
tt.create_file('baz', trans_id)
3248
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
3249
self.build_tree_contents([('tree2/foo', 'qux')])
3251
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3252
pb, tree.basis_tree())
3253
merger.merge_type = Merge3Merger
3256
def test_has_filename(self):
3257
wt = self.make_branch_and_tree('tree')
3258
self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
3259
tt = TransformPreview(wt)
3260
removed_id = tt.trans_id_tree_path('removed')
3261
tt.delete_contents(removed_id)
3262
tt.new_file('new', tt.root, 'contents')
3263
modified_id = tt.trans_id_tree_path('modified')
3264
tt.delete_contents(modified_id)
3265
tt.create_file('modified-contents', modified_id)
3266
self.addCleanup(tt.finalize)
3267
tree = tt.get_preview_tree()
3268
self.assertTrue(tree.has_filename('unmodified'))
3269
self.assertFalse(tree.has_filename('not-present'))
3270
self.assertFalse(tree.has_filename('removed'))
3271
self.assertTrue(tree.has_filename('new'))
3272
self.assertTrue(tree.has_filename('modified'))
3274
def test_is_executable(self):
3275
tree = self.make_branch_and_tree('tree')
3276
preview = TransformPreview(tree)
3277
self.addCleanup(preview.finalize)
3278
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3279
preview_tree = preview.get_preview_tree()
3280
self.assertEqual(False, preview_tree.is_executable('baz-id',
3282
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3284
def test_commit_preview_tree(self):
3285
tree = self.make_branch_and_tree('tree')
3286
rev_id = tree.commit('rev1')
3287
tree.branch.lock_write()
3288
self.addCleanup(tree.branch.unlock)
3289
tt = TransformPreview(tree)
3290
tt.new_file('file', tt.root, 'contents', 'file_id')
3291
self.addCleanup(tt.finalize)
3292
preview = tt.get_preview_tree()
3293
preview.set_parent_ids([rev_id])
3294
builder = tree.branch.get_commit_builder([rev_id])
3295
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3296
builder.finish_inventory()
3297
rev2_id = builder.commit('rev2')
3298
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3299
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3301
def test_ascii_limbo_paths(self):
3302
self.requireFeature(tests.UnicodeFilenameFeature)
3303
branch = self.make_branch('any')
3304
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3305
tt = TransformPreview(tree)
3306
self.addCleanup(tt.finalize)
3307
foo_id = tt.new_directory('', ROOT_PARENT)
3308
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3309
limbo_path = tt._limbo_name(bar_id)
3310
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3313
class FakeSerializer(object):
3314
"""Serializer implementation that simply returns the input.
3316
The input is returned in the order used by pack.ContainerPushParser.
3319
def bytes_record(bytes, names):
3323
class TestSerializeTransform(tests.TestCaseWithTransport):
3325
_test_needs_features = [tests.UnicodeFilenameFeature]
3327
def get_preview(self, tree=None):
3329
tree = self.make_branch_and_tree('tree')
3330
tt = TransformPreview(tree)
3331
self.addCleanup(tt.finalize)
3334
def assertSerializesTo(self, expected, tt):
3335
records = list(tt.serialize(FakeSerializer()))
3336
self.assertEqual(expected, records)
3339
def default_attribs():
3344
'_new_executability': {},
3346
'_tree_path_ids': {'': 'new-0'},
3348
'_removed_contents': [],
3349
'_non_present_ids': {},
3352
def make_records(self, attribs, contents):
3354
(((('attribs'),),), bencode.bencode(attribs))]
3355
records.extend([(((n, k),), c) for n, k, c in contents])
3358
def creation_records(self):
3359
attribs = self.default_attribs()
3360
attribs['_id_number'] = 3
3361
attribs['_new_name'] = {
3362
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3363
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3364
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3365
attribs['_new_executability'] = {'new-1': 1}
3367
('new-1', 'file', 'i 1\nbar\n'),
3368
('new-2', 'directory', ''),
3370
return self.make_records(attribs, contents)
3372
def test_serialize_creation(self):
3373
tt = self.get_preview()
3374
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3375
tt.new_directory('qux', tt.root, 'quxx')
3376
self.assertSerializesTo(self.creation_records(), tt)
3378
def test_deserialize_creation(self):
3379
tt = self.get_preview()
3380
tt.deserialize(iter(self.creation_records()))
3381
self.assertEqual(3, tt._id_number)
3382
self.assertEqual({'new-1': u'foo\u1234',
3383
'new-2': 'qux'}, tt._new_name)
3384
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3385
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3386
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3387
self.assertEqual({'new-1': True}, tt._new_executability)
3388
self.assertEqual({'new-1': 'file',
3389
'new-2': 'directory'}, tt._new_contents)
3390
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3392
foo_content = foo_limbo.read()
3395
self.assertEqual('bar', foo_content)
3397
def symlink_creation_records(self):
3398
attribs = self.default_attribs()
3399
attribs['_id_number'] = 2
3400
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3401
attribs['_new_parent'] = {'new-1': 'new-0'}
3402
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3403
return self.make_records(attribs, contents)
3405
def test_serialize_symlink_creation(self):
3406
self.requireFeature(tests.SymlinkFeature)
3407
tt = self.get_preview()
3408
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3409
self.assertSerializesTo(self.symlink_creation_records(), tt)
3411
def test_deserialize_symlink_creation(self):
3412
self.requireFeature(tests.SymlinkFeature)
3413
tt = self.get_preview()
3414
tt.deserialize(iter(self.symlink_creation_records()))
3415
abspath = tt._limbo_name('new-1')
3416
foo_content = osutils.readlink(abspath)
3417
self.assertEqual(u'bar\u1234', foo_content)
3419
def make_destruction_preview(self):
3420
tree = self.make_branch_and_tree('.')
3421
self.build_tree([u'foo\u1234', 'bar'])
3422
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3423
return self.get_preview(tree)
3425
def destruction_records(self):
3426
attribs = self.default_attribs()
3427
attribs['_id_number'] = 3
3428
attribs['_removed_id'] = ['new-1']
3429
attribs['_removed_contents'] = ['new-2']
3430
attribs['_tree_path_ids'] = {
3432
u'foo\u1234'.encode('utf-8'): 'new-1',
3435
return self.make_records(attribs, [])
3437
def test_serialize_destruction(self):
3438
tt = self.make_destruction_preview()
3439
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3440
tt.unversion_file(foo_trans_id)
3441
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3442
tt.delete_contents(bar_trans_id)
3443
self.assertSerializesTo(self.destruction_records(), tt)
3445
def test_deserialize_destruction(self):
3446
tt = self.make_destruction_preview()
3447
tt.deserialize(iter(self.destruction_records()))
3448
self.assertEqual({u'foo\u1234': 'new-1',
3450
'': tt.root}, tt._tree_path_ids)
3451
self.assertEqual({'new-1': u'foo\u1234',
3453
tt.root: ''}, tt._tree_id_paths)
3454
self.assertEqual(set(['new-1']), tt._removed_id)
3455
self.assertEqual(set(['new-2']), tt._removed_contents)
3457
def missing_records(self):
3458
attribs = self.default_attribs()
3459
attribs['_id_number'] = 2
3460
attribs['_non_present_ids'] = {
3462
return self.make_records(attribs, [])
3464
def test_serialize_missing(self):
3465
tt = self.get_preview()
3466
boo_trans_id = tt.trans_id_file_id('boo')
3467
self.assertSerializesTo(self.missing_records(), tt)
3469
def test_deserialize_missing(self):
3470
tt = self.get_preview()
3471
tt.deserialize(iter(self.missing_records()))
3472
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3474
def make_modification_preview(self):
3475
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3476
LINES_TWO = 'z\nbb\nx\ndd\n'
3477
tree = self.make_branch_and_tree('tree')
3478
self.build_tree_contents([('tree/file', LINES_ONE)])
3479
tree.add('file', 'file-id')
3480
return self.get_preview(tree), LINES_TWO
3482
def modification_records(self):
3483
attribs = self.default_attribs()
3484
attribs['_id_number'] = 2
3485
attribs['_tree_path_ids'] = {
3488
attribs['_removed_contents'] = ['new-1']
3489
contents = [('new-1', 'file',
3490
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3491
return self.make_records(attribs, contents)
3493
def test_serialize_modification(self):
3494
tt, LINES = self.make_modification_preview()
3495
trans_id = tt.trans_id_file_id('file-id')
3496
tt.delete_contents(trans_id)
3497
tt.create_file(LINES, trans_id)
3498
self.assertSerializesTo(self.modification_records(), tt)
3500
def test_deserialize_modification(self):
3501
tt, LINES = self.make_modification_preview()
3502
tt.deserialize(iter(self.modification_records()))
3503
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3505
def make_kind_change_preview(self):
3506
LINES = 'a\nb\nc\nd\n'
3507
tree = self.make_branch_and_tree('tree')
3508
self.build_tree(['tree/foo/'])
3509
tree.add('foo', 'foo-id')
3510
return self.get_preview(tree), LINES
3512
def kind_change_records(self):
3513
attribs = self.default_attribs()
3514
attribs['_id_number'] = 2
3515
attribs['_tree_path_ids'] = {
3518
attribs['_removed_contents'] = ['new-1']
3519
contents = [('new-1', 'file',
3520
'i 4\na\nb\nc\nd\n\n')]
3521
return self.make_records(attribs, contents)
3523
def test_serialize_kind_change(self):
3524
tt, LINES = self.make_kind_change_preview()
3525
trans_id = tt.trans_id_file_id('foo-id')
3526
tt.delete_contents(trans_id)
3527
tt.create_file(LINES, trans_id)
3528
self.assertSerializesTo(self.kind_change_records(), tt)
3530
def test_deserialize_kind_change(self):
3531
tt, LINES = self.make_kind_change_preview()
3532
tt.deserialize(iter(self.kind_change_records()))
3533
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3535
def make_add_contents_preview(self):
3536
LINES = 'a\nb\nc\nd\n'
3537
tree = self.make_branch_and_tree('tree')
3538
self.build_tree(['tree/foo'])
3540
os.unlink('tree/foo')
3541
return self.get_preview(tree), LINES
3543
def add_contents_records(self):
3544
attribs = self.default_attribs()
3545
attribs['_id_number'] = 2
3546
attribs['_tree_path_ids'] = {
3549
contents = [('new-1', 'file',
3550
'i 4\na\nb\nc\nd\n\n')]
3551
return self.make_records(attribs, contents)
3553
def test_serialize_add_contents(self):
3554
tt, LINES = self.make_add_contents_preview()
3555
trans_id = tt.trans_id_tree_path('foo')
3556
tt.create_file(LINES, trans_id)
3557
self.assertSerializesTo(self.add_contents_records(), tt)
3559
def test_deserialize_add_contents(self):
3560
tt, LINES = self.make_add_contents_preview()
3561
tt.deserialize(iter(self.add_contents_records()))
3562
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3564
def test_get_parents_lines(self):
3565
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3566
LINES_TWO = 'z\nbb\nx\ndd\n'
3567
tree = self.make_branch_and_tree('tree')
3568
self.build_tree_contents([('tree/file', LINES_ONE)])
3569
tree.add('file', 'file-id')
3570
tt = self.get_preview(tree)
3571
trans_id = tt.trans_id_tree_path('file')
3572
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3573
tt._get_parents_lines(trans_id))
3575
def test_get_parents_texts(self):
3576
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3577
LINES_TWO = 'z\nbb\nx\ndd\n'
3578
tree = self.make_branch_and_tree('tree')
3579
self.build_tree_contents([('tree/file', LINES_ONE)])
3580
tree.add('file', 'file-id')
3581
tt = self.get_preview(tree)
3582
trans_id = tt.trans_id_tree_path('file')
3583
self.assertEqual((LINES_ONE,),
3584
tt._get_parents_texts(trans_id))
3587
class TestOrphan(tests.TestCaseWithTransport):
3589
def test_no_orphan_for_transform_preview(self):
3590
tree = self.make_branch_and_tree('tree')
3591
tt = transform.TransformPreview(tree)
3592
self.addCleanup(tt.finalize)
3593
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3595
def _set_orphan_policy(self, wt, policy):
3596
wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
3599
def _prepare_orphan(self, wt):
3600
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3601
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3602
wt.commit('add dir and file ignoring foo')
3603
tt = transform.TreeTransform(wt)
3604
self.addCleanup(tt.finalize)
3605
# dir and bar are deleted
3606
dir_tid = tt.trans_id_tree_path('dir')
3607
file_tid = tt.trans_id_tree_path('dir/file')
3608
orphan_tid = tt.trans_id_tree_path('dir/foo')
3609
tt.delete_contents(file_tid)
3610
tt.unversion_file(file_tid)
3611
tt.delete_contents(dir_tid)
3612
tt.unversion_file(dir_tid)
3613
# There should be a conflict because dir still contain foo
3614
raw_conflicts = tt.find_conflicts()
3615
self.assertLength(1, raw_conflicts)
3616
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3617
return tt, orphan_tid
3619
def test_new_orphan_created(self):
3620
wt = self.make_branch_and_tree('.')
3621
self._set_orphan_policy(wt, 'move')
3622
tt, orphan_tid = self._prepare_orphan(wt)
3625
warnings.append(args[0] % args[1:])
3626
self.overrideAttr(trace, 'warning', warning)
3627
remaining_conflicts = resolve_conflicts(tt)
3628
self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
3630
# Yeah for resolved conflicts !
3631
self.assertLength(0, remaining_conflicts)
3632
# We have a new orphan
3633
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3634
self.assertEquals('bzr-orphans',
3635
tt.final_name(tt.final_parent(orphan_tid)))
3637
def test_never_orphan(self):
3638
wt = self.make_branch_and_tree('.')
3639
self._set_orphan_policy(wt, 'conflict')
3640
tt, orphan_tid = self._prepare_orphan(wt)
3641
remaining_conflicts = resolve_conflicts(tt)
3642
self.assertLength(1, remaining_conflicts)
3643
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3644
remaining_conflicts.pop())
3646
def test_orphan_error(self):
3647
def bogus_orphan(tt, orphan_id, parent_id):
3648
raise transform.OrphaningError(tt.final_name(orphan_id),
3649
tt.final_name(parent_id))
3650
transform.orphaning_registry.register('bogus', bogus_orphan,
3651
'Raise an error when orphaning')
3652
wt = self.make_branch_and_tree('.')
3653
self._set_orphan_policy(wt, 'bogus')
3654
tt, orphan_tid = self._prepare_orphan(wt)
3655
remaining_conflicts = resolve_conflicts(tt)
3656
self.assertLength(1, remaining_conflicts)
3657
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3658
remaining_conflicts.pop())
3660
def test_unknown_orphan_policy(self):
3661
wt = self.make_branch_and_tree('.')
3662
# Set a fictional policy nobody ever implemented
3663
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3664
tt, orphan_tid = self._prepare_orphan(wt)
3667
warnings.append(args[0] % args[1:])
3668
self.overrideAttr(trace, 'warning', warning)
3669
remaining_conflicts = resolve_conflicts(tt)
3670
# We fallback to the default policy which create a conflict
3671
self.assertLength(1, remaining_conflicts)
3672
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3673
remaining_conflicts.pop())
3674
self.assertLength(1, warnings)
3675
self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')