1216
764
old = transform.trans_id_tree_path('old')
1217
765
subdir = transform.trans_id_tree_file_id('subdir-id')
1218
self.assertEqual([], list(transform.iter_changes()))
766
self.assertEqual([], list(transform._iter_changes()))
1219
767
transform.delete_contents(subdir)
1220
768
transform.create_directory(subdir)
1221
769
transform.set_executability(False, old)
1222
770
transform.unversion_file(old)
1223
771
transform.version_file('id-1', old)
1224
772
transform.adjust_path('old', root, old)
1225
self.assertEqual([], list(transform.iter_changes()))
1227
transform.finalize()
1229
def test_rename_count(self):
1230
transform, root = self.get_transform()
1231
transform.new_file('name1', root, 'contents')
1232
self.assertEqual(transform.rename_count, 0)
1234
self.assertEqual(transform.rename_count, 1)
1235
transform2, root = self.get_transform()
1236
transform2.adjust_path('name2', root,
1237
transform2.trans_id_tree_path('name1'))
1238
self.assertEqual(transform2.rename_count, 0)
1240
self.assertEqual(transform2.rename_count, 2)
1242
def test_change_parent(self):
1243
"""Ensure that after we change a parent, the results are still right.
1245
Renames and parent changes on pending transforms can happen as part
1246
of conflict resolution, and are explicitly permitted by the
1249
This test ensures they work correctly with the rename-avoidance
1252
transform, root = self.get_transform()
1253
parent1 = transform.new_directory('parent1', root)
1254
child1 = transform.new_file('child1', parent1, 'contents')
1255
parent2 = transform.new_directory('parent2', root)
1256
transform.adjust_path('child1', parent2, child1)
1258
self.assertPathDoesNotExist(self.wt.abspath('parent1/child1'))
1259
self.assertPathExists(self.wt.abspath('parent2/child1'))
1260
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1261
# no rename for child1 (counting only renames during apply)
1262
self.assertEqual(2, transform.rename_count)
1264
def test_cancel_parent(self):
1265
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1267
This is like the test_change_parent, except that we cancel the parent
1268
before adjusting the path. The transform must detect that the
1269
directory is non-empty, and move children to safe locations.
1271
transform, root = self.get_transform()
1272
parent1 = transform.new_directory('parent1', root)
1273
child1 = transform.new_file('child1', parent1, 'contents')
1274
child2 = transform.new_file('child2', parent1, 'contents')
1276
transform.cancel_creation(parent1)
1278
self.fail('Failed to move child1 before deleting parent1')
1279
transform.cancel_creation(child2)
1280
transform.create_directory(parent1)
1282
transform.cancel_creation(parent1)
1283
# If the transform incorrectly believes that child2 is still in
1284
# parent1's limbo directory, it will try to rename it and fail
1285
# because was already moved by the first cancel_creation.
1287
self.fail('Transform still thinks child2 is a child of parent1')
1288
parent2 = transform.new_directory('parent2', root)
1289
transform.adjust_path('child1', parent2, child1)
1291
self.assertPathDoesNotExist(self.wt.abspath('parent1'))
1292
self.assertPathExists(self.wt.abspath('parent2/child1'))
1293
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1294
self.assertEqual(2, transform.rename_count)
1296
def test_adjust_and_cancel(self):
1297
"""Make sure adjust_path keeps track of limbo children properly"""
1298
transform, root = self.get_transform()
1299
parent1 = transform.new_directory('parent1', root)
1300
child1 = transform.new_file('child1', parent1, 'contents')
1301
parent2 = transform.new_directory('parent2', root)
1302
transform.adjust_path('child1', parent2, child1)
1303
transform.cancel_creation(child1)
1305
transform.cancel_creation(parent1)
1306
# if the transform thinks child1 is still in parent1's limbo
1307
# directory, it will attempt to move it and fail.
1309
self.fail('Transform still thinks child1 is a child of parent1')
1310
transform.finalize()
1312
def test_noname_contents(self):
1313
"""TreeTransform should permit deferring naming files."""
1314
transform, root = self.get_transform()
1315
parent = transform.trans_id_file_id('parent-id')
1317
transform.create_directory(parent)
1319
self.fail("Can't handle contents with no name")
1320
transform.finalize()
1322
def test_noname_contents_nested(self):
1323
"""TreeTransform should permit deferring naming files."""
1324
transform, root = self.get_transform()
1325
parent = transform.trans_id_file_id('parent-id')
1327
transform.create_directory(parent)
1329
self.fail("Can't handle contents with no name")
1330
child = transform.new_directory('child', parent)
1331
transform.adjust_path('parent', root, parent)
1333
self.assertPathExists(self.wt.abspath('parent/child'))
1334
self.assertEqual(1, transform.rename_count)
1336
def test_reuse_name(self):
1337
"""Avoid reusing the same limbo name for different files"""
1338
transform, root = self.get_transform()
1339
parent = transform.new_directory('parent', root)
1340
child1 = transform.new_directory('child', parent)
1342
child2 = transform.new_directory('child', parent)
1344
self.fail('Tranform tried to use the same limbo name twice')
1345
transform.adjust_path('child2', parent, child2)
1347
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1348
# child2 is put into top-level limbo because child1 has already
1349
# claimed the direct limbo path when child2 is created. There is no
1350
# advantage in renaming files once they're in top-level limbo, except
1352
self.assertEqual(2, transform.rename_count)
1354
def test_reuse_when_first_moved(self):
1355
"""Don't avoid direct paths when it is safe to use them"""
1356
transform, root = self.get_transform()
1357
parent = transform.new_directory('parent', root)
1358
child1 = transform.new_directory('child', parent)
1359
transform.adjust_path('child1', parent, child1)
1360
child2 = transform.new_directory('child', parent)
1362
# limbo/new-1 => parent
1363
self.assertEqual(1, transform.rename_count)
1365
def test_reuse_after_cancel(self):
1366
"""Don't avoid direct paths when it is safe to use them"""
1367
transform, root = self.get_transform()
1368
parent2 = transform.new_directory('parent2', root)
1369
child1 = transform.new_directory('child1', parent2)
1370
transform.cancel_creation(parent2)
1371
transform.create_directory(parent2)
1372
child2 = transform.new_directory('child1', parent2)
1373
transform.adjust_path('child2', parent2, child1)
1375
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1376
self.assertEqual(2, transform.rename_count)
1378
def test_finalize_order(self):
1379
"""Finalize must be done in child-to-parent order"""
1380
transform, root = self.get_transform()
1381
parent = transform.new_directory('parent', root)
1382
child = transform.new_directory('child', parent)
1384
transform.finalize()
1386
self.fail('Tried to remove parent before child1')
1388
def test_cancel_with_cancelled_child_should_succeed(self):
1389
transform, root = self.get_transform()
1390
parent = transform.new_directory('parent', root)
1391
child = transform.new_directory('child', parent)
1392
transform.cancel_creation(child)
1393
transform.cancel_creation(parent)
1394
transform.finalize()
1396
def test_rollback_on_directory_clash(self):
1398
wt = self.make_branch_and_tree('.')
1399
tt = TreeTransform(wt) # TreeTransform obtains write lock
1401
foo = tt.new_directory('foo', tt.root)
1402
tt.new_file('bar', foo, 'foobar')
1403
baz = tt.new_directory('baz', tt.root)
1404
tt.new_file('qux', baz, 'quux')
1405
# Ask for a rename 'foo' -> 'baz'
1406
tt.adjust_path('baz', tt.root, foo)
1407
# Lie to tt that we've already resolved all conflicts.
1408
tt.apply(no_conflicts=True)
1412
# The rename will fail because the target directory is not empty (but
1413
# raises FileExists anyway).
1414
err = self.assertRaises(errors.FileExists, tt_helper)
1415
self.assertContainsRe(str(err),
1416
"^File exists: .+/baz")
1418
def test_two_directories_clash(self):
1420
wt = self.make_branch_and_tree('.')
1421
tt = TreeTransform(wt) # TreeTransform obtains write lock
1423
foo_1 = tt.new_directory('foo', tt.root)
1424
tt.new_directory('bar', foo_1)
1425
# Adding the same directory with a different content
1426
foo_2 = tt.new_directory('foo', tt.root)
1427
tt.new_directory('baz', foo_2)
1428
# Lie to tt that we've already resolved all conflicts.
1429
tt.apply(no_conflicts=True)
1433
err = self.assertRaises(errors.FileExists, tt_helper)
1434
self.assertContainsRe(str(err),
1435
"^File exists: .+/foo")
1437
def test_two_directories_clash_finalize(self):
1439
wt = self.make_branch_and_tree('.')
1440
tt = TreeTransform(wt) # TreeTransform obtains write lock
1442
foo_1 = tt.new_directory('foo', tt.root)
1443
tt.new_directory('bar', foo_1)
1444
# Adding the same directory with a different content
1445
foo_2 = tt.new_directory('foo', tt.root)
1446
tt.new_directory('baz', foo_2)
1447
# Lie to tt that we've already resolved all conflicts.
1448
tt.apply(no_conflicts=True)
1452
err = self.assertRaises(errors.FileExists, tt_helper)
1453
self.assertContainsRe(str(err),
1454
"^File exists: .+/foo")
1456
def test_file_to_directory(self):
1457
wt = self.make_branch_and_tree('.')
1458
self.build_tree(['foo'])
1461
tt = TreeTransform(wt)
1462
self.addCleanup(tt.finalize)
1463
foo_trans_id = tt.trans_id_tree_path("foo")
1464
tt.delete_contents(foo_trans_id)
1465
tt.create_directory(foo_trans_id)
1466
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1467
tt.create_file(["aa\n"], bar_trans_id)
1468
tt.version_file("bar-1", bar_trans_id)
1470
self.assertPathExists("foo/bar")
1473
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1478
changes = wt.changes_from(wt.basis_tree())
1479
self.assertFalse(changes.has_changed(), changes)
1481
def test_file_to_symlink(self):
1482
self.requireFeature(SymlinkFeature)
1483
wt = self.make_branch_and_tree('.')
1484
self.build_tree(['foo'])
1487
tt = TreeTransform(wt)
1488
self.addCleanup(tt.finalize)
1489
foo_trans_id = tt.trans_id_tree_path("foo")
1490
tt.delete_contents(foo_trans_id)
1491
tt.create_symlink("bar", foo_trans_id)
1493
self.assertPathExists("foo")
1495
self.addCleanup(wt.unlock)
1496
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1499
def test_dir_to_file(self):
1500
wt = self.make_branch_and_tree('.')
1501
self.build_tree(['foo/', 'foo/bar'])
1502
wt.add(['foo', 'foo/bar'])
1504
tt = TreeTransform(wt)
1505
self.addCleanup(tt.finalize)
1506
foo_trans_id = tt.trans_id_tree_path("foo")
1507
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1508
tt.delete_contents(foo_trans_id)
1509
tt.delete_versioned(bar_trans_id)
1510
tt.create_file(["aa\n"], foo_trans_id)
1512
self.assertPathExists("foo")
1514
self.addCleanup(wt.unlock)
1515
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1518
def test_dir_to_hardlink(self):
1519
self.requireFeature(HardlinkFeature)
1520
wt = self.make_branch_and_tree('.')
1521
self.build_tree(['foo/', 'foo/bar'])
1522
wt.add(['foo', 'foo/bar'])
1524
tt = TreeTransform(wt)
1525
self.addCleanup(tt.finalize)
1526
foo_trans_id = tt.trans_id_tree_path("foo")
1527
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1528
tt.delete_contents(foo_trans_id)
1529
tt.delete_versioned(bar_trans_id)
1530
self.build_tree(['baz'])
1531
tt.create_hardlink("baz", foo_trans_id)
1533
self.assertPathExists("foo")
1534
self.assertPathExists("baz")
1536
self.addCleanup(wt.unlock)
1537
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1540
def test_no_final_path(self):
1541
transform, root = self.get_transform()
1542
trans_id = transform.trans_id_file_id('foo')
1543
transform.create_file('bar', trans_id)
1544
transform.cancel_creation(trans_id)
1547
def test_create_from_tree(self):
1548
tree1 = self.make_branch_and_tree('tree1')
1549
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1550
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1551
tree2 = self.make_branch_and_tree('tree2')
1552
tt = TreeTransform(tree2)
1553
foo_trans_id = tt.create_path('foo', tt.root)
1554
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1555
bar_trans_id = tt.create_path('bar', tt.root)
1556
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1558
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1559
self.assertFileEqual('baz', 'tree2/bar')
1561
def test_create_from_tree_bytes(self):
1562
"""Provided lines are used instead of tree content."""
1563
tree1 = self.make_branch_and_tree('tree1')
1564
self.build_tree_contents([('tree1/foo', 'bar'),])
1565
tree1.add('foo', 'foo-id')
1566
tree2 = self.make_branch_and_tree('tree2')
1567
tt = TreeTransform(tree2)
1568
foo_trans_id = tt.create_path('foo', tt.root)
1569
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1571
self.assertFileEqual('qux', 'tree2/foo')
1573
def test_create_from_tree_symlink(self):
1574
self.requireFeature(SymlinkFeature)
1575
tree1 = self.make_branch_and_tree('tree1')
1576
os.symlink('bar', 'tree1/foo')
1577
tree1.add('foo', 'foo-id')
1578
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1579
foo_trans_id = tt.create_path('foo', tt.root)
1580
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1582
self.assertEqual('bar', os.readlink('tree2/foo'))
773
self.assertEqual([], list(transform._iter_changes()))
1585
777
class TransformGroup(object):
1587
778
def __init__(self, dirname, root_id):
1588
779
self.name = dirname
1589
780
os.mkdir(dirname)
1920
1112
target = self.make_branch_and_tree('target')
1921
1113
self.build_tree(['target/name'])
1922
1114
target.add('name')
1923
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1115
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1924
1116
build_tree, source.basis_tree(), target)
1926
def test_build_tree_rename_count(self):
1927
source = self.make_branch_and_tree('source')
1928
self.build_tree(['source/file1', 'source/dir1/'])
1929
source.add(['file1', 'dir1'])
1930
source.commit('add1')
1931
target1 = self.make_branch_and_tree('target1')
1932
transform_result = build_tree(source.basis_tree(), target1)
1933
self.assertEqual(2, transform_result.rename_count)
1935
self.build_tree(['source/dir1/file2'])
1936
source.add(['dir1/file2'])
1937
source.commit('add3')
1938
target2 = self.make_branch_and_tree('target2')
1939
transform_result = build_tree(source.basis_tree(), target2)
1940
# children of non-root directories should not be renamed
1941
self.assertEqual(2, transform_result.rename_count)
1943
def create_ab_tree(self):
1944
"""Create a committed test tree with two files"""
1945
source = self.make_branch_and_tree('source')
1946
self.build_tree_contents([('source/file1', 'A')])
1947
self.build_tree_contents([('source/file2', 'B')])
1948
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1949
source.commit('commit files')
1951
self.addCleanup(source.unlock)
1954
def test_build_tree_accelerator_tree(self):
1955
source = self.create_ab_tree()
1956
self.build_tree_contents([('source/file2', 'C')])
1958
real_source_get_file = source.get_file
1959
def get_file(file_id, path=None):
1960
calls.append(file_id)
1961
return real_source_get_file(file_id, path)
1962
source.get_file = get_file
1963
target = self.make_branch_and_tree('target')
1964
revision_tree = source.basis_tree()
1965
revision_tree.lock_read()
1966
self.addCleanup(revision_tree.unlock)
1967
build_tree(revision_tree, target, source)
1968
self.assertEqual(['file1-id'], calls)
1970
self.addCleanup(target.unlock)
1971
self.assertEqual([], list(target.iter_changes(revision_tree)))
1973
def test_build_tree_accelerator_tree_observes_sha1(self):
1974
source = self.create_ab_tree()
1975
sha1 = osutils.sha_string('A')
1976
target = self.make_branch_and_tree('target')
1978
self.addCleanup(target.unlock)
1979
state = target.current_dirstate()
1980
state._cutoff_time = time.time() + 60
1981
build_tree(source.basis_tree(), target, source)
1982
entry = state._get_entry(0, path_utf8='file1')
1983
self.assertEqual(sha1, entry[1][0][1])
1985
def test_build_tree_accelerator_tree_missing_file(self):
1986
source = self.create_ab_tree()
1987
os.unlink('source/file1')
1988
source.remove(['file2'])
1989
target = self.make_branch_and_tree('target')
1990
revision_tree = source.basis_tree()
1991
revision_tree.lock_read()
1992
self.addCleanup(revision_tree.unlock)
1993
build_tree(revision_tree, target, source)
1995
self.addCleanup(target.unlock)
1996
self.assertEqual([], list(target.iter_changes(revision_tree)))
1998
def test_build_tree_accelerator_wrong_kind(self):
1999
self.requireFeature(SymlinkFeature)
2000
source = self.make_branch_and_tree('source')
2001
self.build_tree_contents([('source/file1', '')])
2002
self.build_tree_contents([('source/file2', '')])
2003
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
2004
source.commit('commit files')
2005
os.unlink('source/file2')
2006
self.build_tree_contents([('source/file2/', 'C')])
2007
os.unlink('source/file1')
2008
os.symlink('file2', 'source/file1')
2010
real_source_get_file = source.get_file
2011
def get_file(file_id, path=None):
2012
calls.append(file_id)
2013
return real_source_get_file(file_id, path)
2014
source.get_file = get_file
2015
target = self.make_branch_and_tree('target')
2016
revision_tree = source.basis_tree()
2017
revision_tree.lock_read()
2018
self.addCleanup(revision_tree.unlock)
2019
build_tree(revision_tree, target, source)
2020
self.assertEqual([], calls)
2022
self.addCleanup(target.unlock)
2023
self.assertEqual([], list(target.iter_changes(revision_tree)))
2025
def test_build_tree_hardlink(self):
2026
self.requireFeature(HardlinkFeature)
2027
source = self.create_ab_tree()
2028
target = self.make_branch_and_tree('target')
2029
revision_tree = source.basis_tree()
2030
revision_tree.lock_read()
2031
self.addCleanup(revision_tree.unlock)
2032
build_tree(revision_tree, target, source, hardlink=True)
2034
self.addCleanup(target.unlock)
2035
self.assertEqual([], list(target.iter_changes(revision_tree)))
2036
source_stat = os.stat('source/file1')
2037
target_stat = os.stat('target/file1')
2038
self.assertEqual(source_stat, target_stat)
2040
# Explicitly disallowing hardlinks should prevent them.
2041
target2 = self.make_branch_and_tree('target2')
2042
build_tree(revision_tree, target2, source, hardlink=False)
2044
self.addCleanup(target2.unlock)
2045
self.assertEqual([], list(target2.iter_changes(revision_tree)))
2046
source_stat = os.stat('source/file1')
2047
target2_stat = os.stat('target2/file1')
2048
self.assertNotEqual(source_stat, target2_stat)
2050
def test_build_tree_accelerator_tree_moved(self):
2051
source = self.make_branch_and_tree('source')
2052
self.build_tree_contents([('source/file1', 'A')])
2053
source.add(['file1'], ['file1-id'])
2054
source.commit('commit files')
2055
source.rename_one('file1', 'file2')
2057
self.addCleanup(source.unlock)
2058
target = self.make_branch_and_tree('target')
2059
revision_tree = source.basis_tree()
2060
revision_tree.lock_read()
2061
self.addCleanup(revision_tree.unlock)
2062
build_tree(revision_tree, target, source)
2064
self.addCleanup(target.unlock)
2065
self.assertEqual([], list(target.iter_changes(revision_tree)))
2067
def test_build_tree_hardlinks_preserve_execute(self):
2068
self.requireFeature(HardlinkFeature)
2069
source = self.create_ab_tree()
2070
tt = TreeTransform(source)
2071
trans_id = tt.trans_id_tree_file_id('file1-id')
2072
tt.set_executability(True, trans_id)
2074
self.assertTrue(source.is_executable('file1-id'))
2075
target = self.make_branch_and_tree('target')
2076
revision_tree = source.basis_tree()
2077
revision_tree.lock_read()
2078
self.addCleanup(revision_tree.unlock)
2079
build_tree(revision_tree, target, source, hardlink=True)
2081
self.addCleanup(target.unlock)
2082
self.assertEqual([], list(target.iter_changes(revision_tree)))
2083
self.assertTrue(source.is_executable('file1-id'))
2085
def install_rot13_content_filter(self, pattern):
2087
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2088
# below, but that looks a bit... hard to read even if it's exactly
2090
original_registry = filters._reset_registry()
2091
def restore_registry():
2092
filters._reset_registry(original_registry)
2093
self.addCleanup(restore_registry)
2094
def rot13(chunks, context=None):
2095
return [''.join(chunks).encode('rot13')]
2096
rot13filter = filters.ContentFilter(rot13, rot13)
2097
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
2098
os.mkdir(self.test_home_dir + '/.bazaar')
2099
rules_filename = self.test_home_dir + '/.bazaar/rules'
2100
f = open(rules_filename, 'wb')
2101
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2103
def uninstall_rules():
2104
os.remove(rules_filename)
2106
self.addCleanup(uninstall_rules)
2109
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2110
"""build_tree will not hardlink files that have content filtering rules
2111
applied to them (but will still hardlink other files from the same tree
2114
self.requireFeature(HardlinkFeature)
2115
self.install_rot13_content_filter('file1')
2116
source = self.create_ab_tree()
2117
target = self.make_branch_and_tree('target')
2118
revision_tree = source.basis_tree()
2119
revision_tree.lock_read()
2120
self.addCleanup(revision_tree.unlock)
2121
build_tree(revision_tree, target, source, hardlink=True)
2123
self.addCleanup(target.unlock)
2124
self.assertEqual([], list(target.iter_changes(revision_tree)))
2125
source_stat = os.stat('source/file1')
2126
target_stat = os.stat('target/file1')
2127
self.assertNotEqual(source_stat, target_stat)
2128
source_stat = os.stat('source/file2')
2129
target_stat = os.stat('target/file2')
2130
self.assertEqualStat(source_stat, target_stat)
2132
def test_case_insensitive_build_tree_inventory(self):
2133
if (tests.CaseInsensitiveFilesystemFeature.available()
2134
or tests.CaseInsCasePresFilenameFeature.available()):
2135
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2136
source = self.make_branch_and_tree('source')
2137
self.build_tree(['source/file', 'source/FILE'])
2138
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2139
source.commit('added files')
2140
# Don't try this at home, kids!
2141
# Force the tree to report that it is case insensitive
2142
target = self.make_branch_and_tree('target')
2143
target.case_sensitive = False
2144
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2145
self.assertEqual('file.moved', target.id2path('lower-id'))
2146
self.assertEqual('FILE', target.id2path('upper-id'))
2148
def test_build_tree_observes_sha(self):
2149
source = self.make_branch_and_tree('source')
2150
self.build_tree(['source/file1', 'source/dir/', 'source/dir/file2'])
2151
source.add(['file1', 'dir', 'dir/file2'],
2152
['file1-id', 'dir-id', 'file2-id'])
2153
source.commit('new files')
2154
target = self.make_branch_and_tree('target')
2156
self.addCleanup(target.unlock)
2157
# We make use of the fact that DirState caches its cutoff time. So we
2158
# set the 'safe' time to one minute in the future.
2159
state = target.current_dirstate()
2160
state._cutoff_time = time.time() + 60
2161
build_tree(source.basis_tree(), target)
2162
entry1_sha = osutils.sha_file_by_name('source/file1')
2163
entry2_sha = osutils.sha_file_by_name('source/dir/file2')
2164
# entry[1] is the state information, entry[1][0] is the state of the
2165
# working tree, entry[1][0][1] is the sha value for the current working
2167
entry1 = state._get_entry(0, path_utf8='file1')
2168
self.assertEqual(entry1_sha, entry1[1][0][1])
2169
# The 'size' field must also be set.
2170
self.assertEqual(25, entry1[1][0][2])
2171
entry1_state = entry1[1][0]
2172
entry2 = state._get_entry(0, path_utf8='dir/file2')
2173
self.assertEqual(entry2_sha, entry2[1][0][1])
2174
self.assertEqual(29, entry2[1][0][2])
2175
entry2_state = entry2[1][0]
2176
# Now, make sure that we don't have to re-read the content. The
2177
# packed_stat should match exactly.
2178
self.assertEqual(entry1_sha, target.get_file_sha1('file1-id', 'file1'))
2179
self.assertEqual(entry2_sha,
2180
target.get_file_sha1('file2-id', 'dir/file2'))
2181
self.assertEqual(entry1_state, entry1[1][0])
2182
self.assertEqual(entry2_state, entry2[1][0])
2185
class TestCommitTransform(tests.TestCaseWithTransport):
2187
def get_branch(self):
2188
tree = self.make_branch_and_tree('tree')
2190
self.addCleanup(tree.unlock)
2191
tree.commit('empty commit')
2194
def get_branch_and_transform(self):
2195
branch = self.get_branch()
2196
tt = TransformPreview(branch.basis_tree())
2197
self.addCleanup(tt.finalize)
2200
def test_commit_wrong_basis(self):
2201
branch = self.get_branch()
2202
basis = branch.repository.revision_tree(
2203
_mod_revision.NULL_REVISION)
2204
tt = TransformPreview(basis)
2205
self.addCleanup(tt.finalize)
2206
e = self.assertRaises(ValueError, tt.commit, branch, '')
2207
self.assertEqual('TreeTransform not based on branch basis: null:',
2210
def test_empy_commit(self):
2211
branch, tt = self.get_branch_and_transform()
2212
rev = tt.commit(branch, 'my message')
2213
self.assertEqual(2, branch.revno())
2214
repo = branch.repository
2215
self.assertEqual('my message', repo.get_revision(rev).message)
2217
def test_merge_parents(self):
2218
branch, tt = self.get_branch_and_transform()
2219
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2220
self.assertEqual(['rev1b', 'rev1c'],
2221
branch.basis_tree().get_parent_ids()[1:])
2223
def test_first_commit(self):
2224
branch = self.make_branch('branch')
2226
self.addCleanup(branch.unlock)
2227
tt = TransformPreview(branch.basis_tree())
2228
self.addCleanup(tt.finalize)
2229
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2230
rev = tt.commit(branch, 'my message')
2231
self.assertEqual([], branch.basis_tree().get_parent_ids())
2232
self.assertNotEqual(_mod_revision.NULL_REVISION,
2233
branch.last_revision())
2235
def test_first_commit_with_merge_parents(self):
2236
branch = self.make_branch('branch')
2238
self.addCleanup(branch.unlock)
2239
tt = TransformPreview(branch.basis_tree())
2240
self.addCleanup(tt.finalize)
2241
e = self.assertRaises(ValueError, tt.commit, branch,
2242
'my message', ['rev1b-id'])
2243
self.assertEqual('Cannot supply merge parents for first commit.',
2245
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2247
def test_add_files(self):
2248
branch, tt = self.get_branch_and_transform()
2249
tt.new_file('file', tt.root, 'contents', 'file-id')
2250
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2251
if SymlinkFeature.available():
2252
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2253
rev = tt.commit(branch, 'message')
2254
tree = branch.basis_tree()
2255
self.assertEqual('file', tree.id2path('file-id'))
2256
self.assertEqual('contents', tree.get_file_text('file-id'))
2257
self.assertEqual('dir', tree.id2path('dir-id'))
2258
if SymlinkFeature.available():
2259
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2260
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2262
def test_add_unversioned(self):
2263
branch, tt = self.get_branch_and_transform()
2264
tt.new_file('file', tt.root, 'contents')
2265
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2266
'message', strict=True)
2268
def test_modify_strict(self):
2269
branch, tt = self.get_branch_and_transform()
2270
tt.new_file('file', tt.root, 'contents', 'file-id')
2271
tt.commit(branch, 'message', strict=True)
2272
tt = TransformPreview(branch.basis_tree())
2273
self.addCleanup(tt.finalize)
2274
trans_id = tt.trans_id_file_id('file-id')
2275
tt.delete_contents(trans_id)
2276
tt.create_file('contents', trans_id)
2277
tt.commit(branch, 'message', strict=True)
2279
def test_commit_malformed(self):
2280
"""Committing a malformed transform should raise an exception.
2282
In this case, we are adding a file without adding its parent.
2284
branch, tt = self.get_branch_and_transform()
2285
parent_id = tt.trans_id_file_id('parent-id')
2286
tt.new_file('file', parent_id, 'contents', 'file-id')
2287
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2290
def test_commit_rich_revision_data(self):
2291
branch, tt = self.get_branch_and_transform()
2292
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2293
committer='me <me@example.com>',
2294
revprops={'foo': 'bar'}, revision_id='revid-1',
2295
authors=['Author1 <author1@example.com>',
2296
'Author2 <author2@example.com>',
2298
self.assertEqual('revid-1', rev_id)
2299
revision = branch.repository.get_revision(rev_id)
2300
self.assertEqual(1, revision.timestamp)
2301
self.assertEqual(43201, revision.timezone)
2302
self.assertEqual('me <me@example.com>', revision.committer)
2303
self.assertEqual(['Author1 <author1@example.com>',
2304
'Author2 <author2@example.com>'],
2305
revision.get_apparent_authors())
2306
del revision.properties['authors']
2307
self.assertEqual({'foo': 'bar',
2308
'branch-nick': 'tree'},
2309
revision.properties)
2311
def test_no_explicit_revprops(self):
2312
branch, tt = self.get_branch_and_transform()
2313
rev_id = tt.commit(branch, 'message', authors=[
2314
'Author1 <author1@example.com>',
2315
'Author2 <author2@example.com>', ])
2316
revision = branch.repository.get_revision(rev_id)
2317
self.assertEqual(['Author1 <author1@example.com>',
2318
'Author2 <author2@example.com>'],
2319
revision.get_apparent_authors())
2320
self.assertEqual('tree', revision.properties['branch-nick'])
2323
class TestBackupName(tests.TestCase):
2325
def test_deprecations(self):
2326
class MockTransform(object):
2328
def has_named_child(self, by_parent, parent_id, name):
2329
return name in by_parent.get(parent_id, [])
2331
class MockEntry(object):
2334
object.__init__(self)
1119
class MockTransform(object):
1121
def has_named_child(self, by_parent, parent_id, name):
1122
for child_id in by_parent[parent_id]:
1126
elif name == "name.~%s~" % child_id:
1130
class MockEntry(object):
1132
object.__init__(self)
1135
class TestGetBackupName(TestCase):
1136
def test_get_backup_name(self):
2337
1137
tt = MockTransform()
2338
name1 = self.applyDeprecated(
2339
symbol_versioning.deprecated_in((2, 3, 0)),
2340
transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
2341
self.assertEqual('name.~1~', name1)
2342
name2 = self.applyDeprecated(
2343
symbol_versioning.deprecated_in((2, 3, 0)),
2344
transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
2345
self.assertEqual('name.~2~', name2)
2348
class TestFileMover(tests.TestCaseWithTransport):
2350
def test_file_mover(self):
2351
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2352
mover = _FileMover()
2353
mover.rename('a', 'q')
2354
self.assertPathExists('q')
2355
self.assertPathDoesNotExist('a')
2356
self.assertPathExists('q/b')
2357
self.assertPathExists('c')
2358
self.assertPathExists('c/d')
2360
def test_pre_delete_rollback(self):
2361
self.build_tree(['a/'])
2362
mover = _FileMover()
2363
mover.pre_delete('a', 'q')
2364
self.assertPathExists('q')
2365
self.assertPathDoesNotExist('a')
2367
self.assertPathDoesNotExist('q')
2368
self.assertPathExists('a')
2370
def test_apply_deletions(self):
2371
self.build_tree(['a/', 'b/'])
2372
mover = _FileMover()
2373
mover.pre_delete('a', 'q')
2374
mover.pre_delete('b', 'r')
2375
self.assertPathExists('q')
2376
self.assertPathExists('r')
2377
self.assertPathDoesNotExist('a')
2378
self.assertPathDoesNotExist('b')
2379
mover.apply_deletions()
2380
self.assertPathDoesNotExist('q')
2381
self.assertPathDoesNotExist('r')
2382
self.assertPathDoesNotExist('a')
2383
self.assertPathDoesNotExist('b')
2385
def test_file_mover_rollback(self):
2386
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2387
mover = _FileMover()
2388
mover.rename('c/d', 'c/f')
2389
mover.rename('c/e', 'c/d')
2391
mover.rename('a', 'c')
2392
except errors.FileExists, e:
2394
self.assertPathExists('a')
2395
self.assertPathExists('c/d')
2398
class Bogus(Exception):
2402
class TestTransformRollback(tests.TestCaseWithTransport):
2404
class ExceptionFileMover(_FileMover):
2406
def __init__(self, bad_source=None, bad_target=None):
2407
_FileMover.__init__(self)
2408
self.bad_source = bad_source
2409
self.bad_target = bad_target
2411
def rename(self, source, target):
2412
if (self.bad_source is not None and
2413
source.endswith(self.bad_source)):
2415
elif (self.bad_target is not None and
2416
target.endswith(self.bad_target)):
2419
_FileMover.rename(self, source, target)
2421
def test_rollback_rename(self):
2422
tree = self.make_branch_and_tree('.')
2423
self.build_tree(['a/', 'a/b'])
2424
tt = TreeTransform(tree)
2425
self.addCleanup(tt.finalize)
2426
a_id = tt.trans_id_tree_path('a')
2427
tt.adjust_path('c', tt.root, a_id)
2428
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2429
self.assertRaises(Bogus, tt.apply,
2430
_mover=self.ExceptionFileMover(bad_source='a'))
2431
self.assertPathExists('a')
2432
self.assertPathExists('a/b')
2434
self.assertPathExists('c')
2435
self.assertPathExists('c/d')
2437
def test_rollback_rename_into_place(self):
2438
tree = self.make_branch_and_tree('.')
2439
self.build_tree(['a/', 'a/b'])
2440
tt = TreeTransform(tree)
2441
self.addCleanup(tt.finalize)
2442
a_id = tt.trans_id_tree_path('a')
2443
tt.adjust_path('c', tt.root, a_id)
2444
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2445
self.assertRaises(Bogus, tt.apply,
2446
_mover=self.ExceptionFileMover(bad_target='c/d'))
2447
self.assertPathExists('a')
2448
self.assertPathExists('a/b')
2450
self.assertPathExists('c')
2451
self.assertPathExists('c/d')
2453
def test_rollback_deletion(self):
2454
tree = self.make_branch_and_tree('.')
2455
self.build_tree(['a/', 'a/b'])
2456
tt = TreeTransform(tree)
2457
self.addCleanup(tt.finalize)
2458
a_id = tt.trans_id_tree_path('a')
2459
tt.delete_contents(a_id)
2460
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2461
self.assertRaises(Bogus, tt.apply,
2462
_mover=self.ExceptionFileMover(bad_target='d'))
2463
self.assertPathExists('a')
2464
self.assertPathExists('a/b')
2467
class TestTransformMissingParent(tests.TestCaseWithTransport):
2469
def make_tt_with_versioned_dir(self):
2470
wt = self.make_branch_and_tree('.')
2471
self.build_tree(['dir/',])
2472
wt.add(['dir'], ['dir-id'])
2473
wt.commit('Create dir')
2474
tt = TreeTransform(wt)
2475
self.addCleanup(tt.finalize)
2478
def test_resolve_create_parent_for_versioned_file(self):
2479
wt, tt = self.make_tt_with_versioned_dir()
2480
dir_tid = tt.trans_id_tree_file_id('dir-id')
2481
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2482
tt.delete_contents(dir_tid)
2483
tt.unversion_file(dir_tid)
2484
conflicts = resolve_conflicts(tt)
2485
# one conflict for the missing directory, one for the unversioned
2487
self.assertLength(2, conflicts)
2489
def test_non_versioned_file_create_conflict(self):
2490
wt, tt = self.make_tt_with_versioned_dir()
2491
dir_tid = tt.trans_id_tree_file_id('dir-id')
2492
tt.new_file('file', dir_tid, 'Contents')
2493
tt.delete_contents(dir_tid)
2494
tt.unversion_file(dir_tid)
2495
conflicts = resolve_conflicts(tt)
2496
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2497
self.assertLength(1, conflicts)
2498
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2502
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2503
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2505
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2506
('', ''), ('directory', 'directory'), (False, False))
2509
class TestTransformPreview(tests.TestCaseWithTransport):
2511
def create_tree(self):
2512
tree = self.make_branch_and_tree('.')
2513
self.build_tree_contents([('a', 'content 1')])
2514
tree.set_root_id('TREE_ROOT')
2515
tree.add('a', 'a-id')
2516
tree.commit('rev1', rev_id='rev1')
2517
return tree.branch.repository.revision_tree('rev1')
2519
def get_empty_preview(self):
2520
repository = self.make_repository('repo')
2521
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2522
preview = TransformPreview(tree)
2523
self.addCleanup(preview.finalize)
2526
def test_transform_preview(self):
2527
revision_tree = self.create_tree()
2528
preview = TransformPreview(revision_tree)
2529
self.addCleanup(preview.finalize)
2531
def test_transform_preview_tree(self):
2532
revision_tree = self.create_tree()
2533
preview = TransformPreview(revision_tree)
2534
self.addCleanup(preview.finalize)
2535
preview.get_preview_tree()
2537
def test_transform_new_file(self):
2538
revision_tree = self.create_tree()
2539
preview = TransformPreview(revision_tree)
2540
self.addCleanup(preview.finalize)
2541
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2542
preview_tree = preview.get_preview_tree()
2543
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2545
preview_tree.get_file('file2-id').read(), 'content B\n')
2547
def test_diff_preview_tree(self):
2548
revision_tree = self.create_tree()
2549
preview = TransformPreview(revision_tree)
2550
self.addCleanup(preview.finalize)
2551
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2552
preview_tree = preview.get_preview_tree()
2554
show_diff_trees(revision_tree, preview_tree, out)
2555
lines = out.getvalue().splitlines()
2556
self.assertEqual(lines[0], "=== added file 'file2'")
2557
# 3 lines of diff administrivia
2558
self.assertEqual(lines[4], "+content B")
2560
def test_transform_conflicts(self):
2561
revision_tree = self.create_tree()
2562
preview = TransformPreview(revision_tree)
2563
self.addCleanup(preview.finalize)
2564
preview.new_file('a', preview.root, 'content 2')
2565
resolve_conflicts(preview)
2566
trans_id = preview.trans_id_file_id('a-id')
2567
self.assertEqual('a.moved', preview.final_name(trans_id))
2569
def get_tree_and_preview_tree(self):
2570
revision_tree = self.create_tree()
2571
preview = TransformPreview(revision_tree)
2572
self.addCleanup(preview.finalize)
2573
a_trans_id = preview.trans_id_file_id('a-id')
2574
preview.delete_contents(a_trans_id)
2575
preview.create_file('b content', a_trans_id)
2576
preview_tree = preview.get_preview_tree()
2577
return revision_tree, preview_tree
2579
def test_iter_changes(self):
2580
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2581
root = revision_tree.inventory.root.file_id
2582
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2583
(root, root), ('a', 'a'), ('file', 'file'),
2585
list(preview_tree.iter_changes(revision_tree)))
2587
def test_include_unchanged_succeeds(self):
2588
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2589
changes = preview_tree.iter_changes(revision_tree,
2590
include_unchanged=True)
2591
root = revision_tree.inventory.root.file_id
2593
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2595
def test_specific_files(self):
2596
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2597
changes = preview_tree.iter_changes(revision_tree,
2598
specific_files=[''])
2599
self.assertEqual([A_ENTRY], list(changes))
2601
def test_want_unversioned(self):
2602
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2603
changes = preview_tree.iter_changes(revision_tree,
2604
want_unversioned=True)
2605
self.assertEqual([A_ENTRY], list(changes))
2607
def test_ignore_extra_trees_no_specific_files(self):
2608
# extra_trees is harmless without specific_files, so we'll silently
2609
# accept it, even though we won't use it.
2610
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2611
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2613
def test_ignore_require_versioned_no_specific_files(self):
2614
# require_versioned is meaningless without specific_files.
2615
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2616
preview_tree.iter_changes(revision_tree, require_versioned=False)
2618
def test_ignore_pb(self):
2619
# pb could be supported, but TT.iter_changes doesn't support it.
2620
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2621
preview_tree.iter_changes(revision_tree)
2623
def test_kind(self):
2624
revision_tree = self.create_tree()
2625
preview = TransformPreview(revision_tree)
2626
self.addCleanup(preview.finalize)
2627
preview.new_file('file', preview.root, 'contents', 'file-id')
2628
preview.new_directory('directory', preview.root, 'dir-id')
2629
preview_tree = preview.get_preview_tree()
2630
self.assertEqual('file', preview_tree.kind('file-id'))
2631
self.assertEqual('directory', preview_tree.kind('dir-id'))
2633
def test_get_file_mtime(self):
2634
preview = self.get_empty_preview()
2635
file_trans_id = preview.new_file('file', preview.root, 'contents',
2637
limbo_path = preview._limbo_name(file_trans_id)
2638
preview_tree = preview.get_preview_tree()
2639
self.assertEqual(os.stat(limbo_path).st_mtime,
2640
preview_tree.get_file_mtime('file-id'))
2642
def test_get_file_mtime_renamed(self):
2643
work_tree = self.make_branch_and_tree('tree')
2644
self.build_tree(['tree/file'])
2645
work_tree.add('file', 'file-id')
2646
preview = TransformPreview(work_tree)
2647
self.addCleanup(preview.finalize)
2648
file_trans_id = preview.trans_id_tree_file_id('file-id')
2649
preview.adjust_path('renamed', preview.root, file_trans_id)
2650
preview_tree = preview.get_preview_tree()
2651
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2652
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2654
def test_get_file(self):
2655
preview = self.get_empty_preview()
2656
preview.new_file('file', preview.root, 'contents', 'file-id')
2657
preview_tree = preview.get_preview_tree()
2658
tree_file = preview_tree.get_file('file-id')
2660
self.assertEqual('contents', tree_file.read())
2664
def test_get_symlink_target(self):
2665
self.requireFeature(SymlinkFeature)
2666
preview = self.get_empty_preview()
2667
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2668
preview_tree = preview.get_preview_tree()
2669
self.assertEqual('target',
2670
preview_tree.get_symlink_target('symlink-id'))
2672
def test_all_file_ids(self):
2673
tree = self.make_branch_and_tree('tree')
2674
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2675
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2676
preview = TransformPreview(tree)
2677
self.addCleanup(preview.finalize)
2678
preview.unversion_file(preview.trans_id_file_id('b-id'))
2679
c_trans_id = preview.trans_id_file_id('c-id')
2680
preview.unversion_file(c_trans_id)
2681
preview.version_file('c-id', c_trans_id)
2682
preview_tree = preview.get_preview_tree()
2683
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2684
preview_tree.all_file_ids())
2686
def test_path2id_deleted_unchanged(self):
2687
tree = self.make_branch_and_tree('tree')
2688
self.build_tree(['tree/unchanged', 'tree/deleted'])
2689
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2690
preview = TransformPreview(tree)
2691
self.addCleanup(preview.finalize)
2692
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2693
preview_tree = preview.get_preview_tree()
2694
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2695
self.assertIs(None, preview_tree.path2id('deleted'))
2697
def test_path2id_created(self):
2698
tree = self.make_branch_and_tree('tree')
2699
self.build_tree(['tree/unchanged'])
2700
tree.add(['unchanged'], ['unchanged-id'])
2701
preview = TransformPreview(tree)
2702
self.addCleanup(preview.finalize)
2703
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2704
'contents', 'new-id')
2705
preview_tree = preview.get_preview_tree()
2706
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2708
def test_path2id_moved(self):
2709
tree = self.make_branch_and_tree('tree')
2710
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2711
tree.add(['old_parent', 'old_parent/child'],
2712
['old_parent-id', 'child-id'])
2713
preview = TransformPreview(tree)
2714
self.addCleanup(preview.finalize)
2715
new_parent = preview.new_directory('new_parent', preview.root,
2717
preview.adjust_path('child', new_parent,
2718
preview.trans_id_file_id('child-id'))
2719
preview_tree = preview.get_preview_tree()
2720
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2721
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2723
def test_path2id_renamed_parent(self):
2724
tree = self.make_branch_and_tree('tree')
2725
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2726
tree.add(['old_name', 'old_name/child'],
2727
['parent-id', 'child-id'])
2728
preview = TransformPreview(tree)
2729
self.addCleanup(preview.finalize)
2730
preview.adjust_path('new_name', preview.root,
2731
preview.trans_id_file_id('parent-id'))
2732
preview_tree = preview.get_preview_tree()
2733
self.assertIs(None, preview_tree.path2id('old_name/child'))
2734
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2736
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2737
preview_tree = tt.get_preview_tree()
2738
preview_result = list(preview_tree.iter_entries_by_dir(
2742
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2743
self.assertEqual(actual_result, preview_result)
2745
def test_iter_entries_by_dir_new(self):
2746
tree = self.make_branch_and_tree('tree')
2747
tt = TreeTransform(tree)
2748
tt.new_file('new', tt.root, 'contents', 'new-id')
2749
self.assertMatchingIterEntries(tt)
2751
def test_iter_entries_by_dir_deleted(self):
2752
tree = self.make_branch_and_tree('tree')
2753
self.build_tree(['tree/deleted'])
2754
tree.add('deleted', 'deleted-id')
2755
tt = TreeTransform(tree)
2756
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2757
self.assertMatchingIterEntries(tt)
2759
def test_iter_entries_by_dir_unversioned(self):
2760
tree = self.make_branch_and_tree('tree')
2761
self.build_tree(['tree/removed'])
2762
tree.add('removed', 'removed-id')
2763
tt = TreeTransform(tree)
2764
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2765
self.assertMatchingIterEntries(tt)
2767
def test_iter_entries_by_dir_moved(self):
2768
tree = self.make_branch_and_tree('tree')
2769
self.build_tree(['tree/moved', 'tree/new_parent/'])
2770
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2771
tt = TreeTransform(tree)
2772
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2773
tt.trans_id_file_id('moved-id'))
2774
self.assertMatchingIterEntries(tt)
2776
def test_iter_entries_by_dir_specific_file_ids(self):
2777
tree = self.make_branch_and_tree('tree')
2778
tree.set_root_id('tree-root-id')
2779
self.build_tree(['tree/parent/', 'tree/parent/child'])
2780
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2781
tt = TreeTransform(tree)
2782
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2784
def test_symlink_content_summary(self):
2785
self.requireFeature(SymlinkFeature)
2786
preview = self.get_empty_preview()
2787
preview.new_symlink('path', preview.root, 'target', 'path-id')
2788
summary = preview.get_preview_tree().path_content_summary('path')
2789
self.assertEqual(('symlink', None, None, 'target'), summary)
2791
def test_missing_content_summary(self):
2792
preview = self.get_empty_preview()
2793
summary = preview.get_preview_tree().path_content_summary('path')
2794
self.assertEqual(('missing', None, None, None), summary)
2796
def test_deleted_content_summary(self):
2797
tree = self.make_branch_and_tree('tree')
2798
self.build_tree(['tree/path/'])
2800
preview = TransformPreview(tree)
2801
self.addCleanup(preview.finalize)
2802
preview.delete_contents(preview.trans_id_tree_path('path'))
2803
summary = preview.get_preview_tree().path_content_summary('path')
2804
self.assertEqual(('missing', None, None, None), summary)
2806
def test_file_content_summary_executable(self):
2807
preview = self.get_empty_preview()
2808
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2809
preview.set_executability(True, path_id)
2810
summary = preview.get_preview_tree().path_content_summary('path')
2811
self.assertEqual(4, len(summary))
2812
self.assertEqual('file', summary[0])
2813
# size must be known
2814
self.assertEqual(len('contents'), summary[1])
2816
self.assertEqual(True, summary[2])
2817
# will not have hash (not cheap to determine)
2818
self.assertIs(None, summary[3])
2820
def test_change_executability(self):
2821
tree = self.make_branch_and_tree('tree')
2822
self.build_tree(['tree/path'])
2824
preview = TransformPreview(tree)
2825
self.addCleanup(preview.finalize)
2826
path_id = preview.trans_id_tree_path('path')
2827
preview.set_executability(True, path_id)
2828
summary = preview.get_preview_tree().path_content_summary('path')
2829
self.assertEqual(True, summary[2])
2831
def test_file_content_summary_non_exec(self):
2832
preview = self.get_empty_preview()
2833
preview.new_file('path', preview.root, 'contents', 'path-id')
2834
summary = preview.get_preview_tree().path_content_summary('path')
2835
self.assertEqual(4, len(summary))
2836
self.assertEqual('file', summary[0])
2837
# size must be known
2838
self.assertEqual(len('contents'), summary[1])
2840
self.assertEqual(False, summary[2])
2841
# will not have hash (not cheap to determine)
2842
self.assertIs(None, summary[3])
2844
def test_dir_content_summary(self):
2845
preview = self.get_empty_preview()
2846
preview.new_directory('path', preview.root, 'path-id')
2847
summary = preview.get_preview_tree().path_content_summary('path')
2848
self.assertEqual(('directory', None, None, None), summary)
2850
def test_tree_content_summary(self):
2851
preview = self.get_empty_preview()
2852
path = preview.new_directory('path', preview.root, 'path-id')
2853
preview.set_tree_reference('rev-1', path)
2854
summary = preview.get_preview_tree().path_content_summary('path')
2855
self.assertEqual(4, len(summary))
2856
self.assertEqual('tree-reference', summary[0])
2858
def test_annotate(self):
2859
tree = self.make_branch_and_tree('tree')
2860
self.build_tree_contents([('tree/file', 'a\n')])
2861
tree.add('file', 'file-id')
2862
tree.commit('a', rev_id='one')
2863
self.build_tree_contents([('tree/file', 'a\nb\n')])
2864
preview = TransformPreview(tree)
2865
self.addCleanup(preview.finalize)
2866
file_trans_id = preview.trans_id_file_id('file-id')
2867
preview.delete_contents(file_trans_id)
2868
preview.create_file('a\nb\nc\n', file_trans_id)
2869
preview_tree = preview.get_preview_tree()
2875
annotation = preview_tree.annotate_iter('file-id', 'me:')
2876
self.assertEqual(expected, annotation)
2878
def test_annotate_missing(self):
2879
preview = self.get_empty_preview()
2880
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2881
preview_tree = preview.get_preview_tree()
2887
annotation = preview_tree.annotate_iter('file-id', 'me:')
2888
self.assertEqual(expected, annotation)
2890
def test_annotate_rename(self):
2891
tree = self.make_branch_and_tree('tree')
2892
self.build_tree_contents([('tree/file', 'a\n')])
2893
tree.add('file', 'file-id')
2894
tree.commit('a', rev_id='one')
2895
preview = TransformPreview(tree)
2896
self.addCleanup(preview.finalize)
2897
file_trans_id = preview.trans_id_file_id('file-id')
2898
preview.adjust_path('newname', preview.root, file_trans_id)
2899
preview_tree = preview.get_preview_tree()
2903
annotation = preview_tree.annotate_iter('file-id', 'me:')
2904
self.assertEqual(expected, annotation)
2906
def test_annotate_deleted(self):
2907
tree = self.make_branch_and_tree('tree')
2908
self.build_tree_contents([('tree/file', 'a\n')])
2909
tree.add('file', 'file-id')
2910
tree.commit('a', rev_id='one')
2911
self.build_tree_contents([('tree/file', 'a\nb\n')])
2912
preview = TransformPreview(tree)
2913
self.addCleanup(preview.finalize)
2914
file_trans_id = preview.trans_id_file_id('file-id')
2915
preview.delete_contents(file_trans_id)
2916
preview_tree = preview.get_preview_tree()
2917
annotation = preview_tree.annotate_iter('file-id', 'me:')
2918
self.assertIs(None, annotation)
2920
def test_stored_kind(self):
2921
preview = self.get_empty_preview()
2922
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2923
preview_tree = preview.get_preview_tree()
2924
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2926
def test_is_executable(self):
2927
preview = self.get_empty_preview()
2928
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2929
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2930
preview_tree = preview.get_preview_tree()
2931
self.assertEqual(True, preview_tree.is_executable('file-id'))
2933
def test_get_set_parent_ids(self):
2934
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2935
self.assertEqual([], preview_tree.get_parent_ids())
2936
preview_tree.set_parent_ids(['rev-1'])
2937
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2939
def test_plan_file_merge(self):
2940
work_a = self.make_branch_and_tree('wta')
2941
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2942
work_a.add('file', 'file-id')
2943
base_id = work_a.commit('base version')
2944
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2945
preview = TransformPreview(work_a)
2946
self.addCleanup(preview.finalize)
2947
trans_id = preview.trans_id_file_id('file-id')
2948
preview.delete_contents(trans_id)
2949
preview.create_file('b\nc\nd\ne\n', trans_id)
2950
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2951
tree_a = preview.get_preview_tree()
2952
tree_a.set_parent_ids([base_id])
2954
('killed-a', 'a\n'),
2955
('killed-b', 'b\n'),
2956
('unchanged', 'c\n'),
2957
('unchanged', 'd\n'),
2960
], list(tree_a.plan_file_merge('file-id', tree_b)))
2962
def test_plan_file_merge_revision_tree(self):
2963
work_a = self.make_branch_and_tree('wta')
2964
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2965
work_a.add('file', 'file-id')
2966
base_id = work_a.commit('base version')
2967
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2968
preview = TransformPreview(work_a.basis_tree())
2969
self.addCleanup(preview.finalize)
2970
trans_id = preview.trans_id_file_id('file-id')
2971
preview.delete_contents(trans_id)
2972
preview.create_file('b\nc\nd\ne\n', trans_id)
2973
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2974
tree_a = preview.get_preview_tree()
2975
tree_a.set_parent_ids([base_id])
2977
('killed-a', 'a\n'),
2978
('killed-b', 'b\n'),
2979
('unchanged', 'c\n'),
2980
('unchanged', 'd\n'),
2983
], list(tree_a.plan_file_merge('file-id', tree_b)))
2985
def test_walkdirs(self):
2986
preview = self.get_empty_preview()
2987
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2988
# FIXME: new_directory should mark root.
2989
preview.fixup_new_roots()
2990
preview_tree = preview.get_preview_tree()
2991
file_trans_id = preview.new_file('a', preview.root, 'contents',
2993
expected = [(('', 'tree-root'),
2994
[('a', 'a', 'file', None, 'a-id', 'file')])]
2995
self.assertEqual(expected, list(preview_tree.walkdirs()))
2997
def test_extras(self):
2998
work_tree = self.make_branch_and_tree('tree')
2999
self.build_tree(['tree/removed-file', 'tree/existing-file',
3000
'tree/not-removed-file'])
3001
work_tree.add(['removed-file', 'not-removed-file'])
3002
preview = TransformPreview(work_tree)
3003
self.addCleanup(preview.finalize)
3004
preview.new_file('new-file', preview.root, 'contents')
3005
preview.new_file('new-versioned-file', preview.root, 'contents',
3007
tree = preview.get_preview_tree()
3008
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
3009
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
3012
def test_merge_into_preview(self):
3013
work_tree = self.make_branch_and_tree('tree')
3014
self.build_tree_contents([('tree/file','b\n')])
3015
work_tree.add('file', 'file-id')
3016
work_tree.commit('first commit')
3017
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
3018
self.build_tree_contents([('child/file','b\nc\n')])
3019
child_tree.commit('child commit')
3020
child_tree.lock_write()
3021
self.addCleanup(child_tree.unlock)
3022
work_tree.lock_write()
3023
self.addCleanup(work_tree.unlock)
3024
preview = TransformPreview(work_tree)
3025
self.addCleanup(preview.finalize)
3026
file_trans_id = preview.trans_id_file_id('file-id')
3027
preview.delete_contents(file_trans_id)
3028
preview.create_file('a\nb\n', file_trans_id)
3029
preview_tree = preview.get_preview_tree()
3030
merger = Merger.from_revision_ids(None, preview_tree,
3031
child_tree.branch.last_revision(),
3032
other_branch=child_tree.branch,
3033
tree_branch=work_tree.branch)
3034
merger.merge_type = Merge3Merger
3035
tt = merger.make_merger().make_preview_transform()
3036
self.addCleanup(tt.finalize)
3037
final_tree = tt.get_preview_tree()
3038
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
3040
def test_merge_preview_into_workingtree(self):
3041
tree = self.make_branch_and_tree('tree')
3042
tree.set_root_id('TREE_ROOT')
3043
tt = TransformPreview(tree)
3044
self.addCleanup(tt.finalize)
3045
tt.new_file('name', tt.root, 'content', 'file-id')
3046
tree2 = self.make_branch_and_tree('tree2')
3047
tree2.set_root_id('TREE_ROOT')
3048
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3049
None, tree.basis_tree())
3050
merger.merge_type = Merge3Merger
3053
def test_merge_preview_into_workingtree_handles_conflicts(self):
3054
tree = self.make_branch_and_tree('tree')
3055
self.build_tree_contents([('tree/foo', 'bar')])
3056
tree.add('foo', 'foo-id')
3058
tt = TransformPreview(tree)
3059
self.addCleanup(tt.finalize)
3060
trans_id = tt.trans_id_file_id('foo-id')
3061
tt.delete_contents(trans_id)
3062
tt.create_file('baz', trans_id)
3063
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
3064
self.build_tree_contents([('tree2/foo', 'qux')])
3066
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
3067
pb, tree.basis_tree())
3068
merger.merge_type = Merge3Merger
3071
def test_is_executable(self):
3072
tree = self.make_branch_and_tree('tree')
3073
preview = TransformPreview(tree)
3074
self.addCleanup(preview.finalize)
3075
preview.new_file('foo', preview.root, 'bar', 'baz-id')
3076
preview_tree = preview.get_preview_tree()
3077
self.assertEqual(False, preview_tree.is_executable('baz-id',
3079
self.assertEqual(False, preview_tree.is_executable('baz-id'))
3081
def test_commit_preview_tree(self):
3082
tree = self.make_branch_and_tree('tree')
3083
rev_id = tree.commit('rev1')
3084
tree.branch.lock_write()
3085
self.addCleanup(tree.branch.unlock)
3086
tt = TransformPreview(tree)
3087
tt.new_file('file', tt.root, 'contents', 'file_id')
3088
self.addCleanup(tt.finalize)
3089
preview = tt.get_preview_tree()
3090
preview.set_parent_ids([rev_id])
3091
builder = tree.branch.get_commit_builder([rev_id])
3092
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
3093
builder.finish_inventory()
3094
rev2_id = builder.commit('rev2')
3095
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
3096
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
3098
def test_ascii_limbo_paths(self):
3099
self.requireFeature(tests.UnicodeFilenameFeature)
3100
branch = self.make_branch('any')
3101
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
3102
tt = TransformPreview(tree)
3103
self.addCleanup(tt.finalize)
3104
foo_id = tt.new_directory('', ROOT_PARENT)
3105
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
3106
limbo_path = tt._limbo_name(bar_id)
3107
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
3110
class FakeSerializer(object):
3111
"""Serializer implementation that simply returns the input.
3113
The input is returned in the order used by pack.ContainerPushParser.
3116
def bytes_record(bytes, names):
3120
class TestSerializeTransform(tests.TestCaseWithTransport):
3122
_test_needs_features = [tests.UnicodeFilenameFeature]
3124
def get_preview(self, tree=None):
3126
tree = self.make_branch_and_tree('tree')
3127
tt = TransformPreview(tree)
3128
self.addCleanup(tt.finalize)
3131
def assertSerializesTo(self, expected, tt):
3132
records = list(tt.serialize(FakeSerializer()))
3133
self.assertEqual(expected, records)
3136
def default_attribs():
3141
'_new_executability': {},
3143
'_tree_path_ids': {'': 'new-0'},
3145
'_removed_contents': [],
3146
'_non_present_ids': {},
3149
def make_records(self, attribs, contents):
3151
(((('attribs'),),), bencode.bencode(attribs))]
3152
records.extend([(((n, k),), c) for n, k, c in contents])
3155
def creation_records(self):
3156
attribs = self.default_attribs()
3157
attribs['_id_number'] = 3
3158
attribs['_new_name'] = {
3159
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3160
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3161
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3162
attribs['_new_executability'] = {'new-1': 1}
3164
('new-1', 'file', 'i 1\nbar\n'),
3165
('new-2', 'directory', ''),
3167
return self.make_records(attribs, contents)
3169
def test_serialize_creation(self):
3170
tt = self.get_preview()
3171
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3172
tt.new_directory('qux', tt.root, 'quxx')
3173
self.assertSerializesTo(self.creation_records(), tt)
3175
def test_deserialize_creation(self):
3176
tt = self.get_preview()
3177
tt.deserialize(iter(self.creation_records()))
3178
self.assertEqual(3, tt._id_number)
3179
self.assertEqual({'new-1': u'foo\u1234',
3180
'new-2': 'qux'}, tt._new_name)
3181
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3182
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3183
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3184
self.assertEqual({'new-1': True}, tt._new_executability)
3185
self.assertEqual({'new-1': 'file',
3186
'new-2': 'directory'}, tt._new_contents)
3187
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3189
foo_content = foo_limbo.read()
3192
self.assertEqual('bar', foo_content)
3194
def symlink_creation_records(self):
3195
attribs = self.default_attribs()
3196
attribs['_id_number'] = 2
3197
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3198
attribs['_new_parent'] = {'new-1': 'new-0'}
3199
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3200
return self.make_records(attribs, contents)
3202
def test_serialize_symlink_creation(self):
3203
self.requireFeature(tests.SymlinkFeature)
3204
tt = self.get_preview()
3205
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3206
self.assertSerializesTo(self.symlink_creation_records(), tt)
3208
def test_deserialize_symlink_creation(self):
3209
self.requireFeature(tests.SymlinkFeature)
3210
tt = self.get_preview()
3211
tt.deserialize(iter(self.symlink_creation_records()))
3212
abspath = tt._limbo_name('new-1')
3213
foo_content = osutils.readlink(abspath)
3214
self.assertEqual(u'bar\u1234', foo_content)
3216
def make_destruction_preview(self):
3217
tree = self.make_branch_and_tree('.')
3218
self.build_tree([u'foo\u1234', 'bar'])
3219
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3220
return self.get_preview(tree)
3222
def destruction_records(self):
3223
attribs = self.default_attribs()
3224
attribs['_id_number'] = 3
3225
attribs['_removed_id'] = ['new-1']
3226
attribs['_removed_contents'] = ['new-2']
3227
attribs['_tree_path_ids'] = {
3229
u'foo\u1234'.encode('utf-8'): 'new-1',
3232
return self.make_records(attribs, [])
3234
def test_serialize_destruction(self):
3235
tt = self.make_destruction_preview()
3236
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3237
tt.unversion_file(foo_trans_id)
3238
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3239
tt.delete_contents(bar_trans_id)
3240
self.assertSerializesTo(self.destruction_records(), tt)
3242
def test_deserialize_destruction(self):
3243
tt = self.make_destruction_preview()
3244
tt.deserialize(iter(self.destruction_records()))
3245
self.assertEqual({u'foo\u1234': 'new-1',
3247
'': tt.root}, tt._tree_path_ids)
3248
self.assertEqual({'new-1': u'foo\u1234',
3250
tt.root: ''}, tt._tree_id_paths)
3251
self.assertEqual(set(['new-1']), tt._removed_id)
3252
self.assertEqual(set(['new-2']), tt._removed_contents)
3254
def missing_records(self):
3255
attribs = self.default_attribs()
3256
attribs['_id_number'] = 2
3257
attribs['_non_present_ids'] = {
3259
return self.make_records(attribs, [])
3261
def test_serialize_missing(self):
3262
tt = self.get_preview()
3263
boo_trans_id = tt.trans_id_file_id('boo')
3264
self.assertSerializesTo(self.missing_records(), tt)
3266
def test_deserialize_missing(self):
3267
tt = self.get_preview()
3268
tt.deserialize(iter(self.missing_records()))
3269
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3271
def make_modification_preview(self):
3272
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3273
LINES_TWO = 'z\nbb\nx\ndd\n'
3274
tree = self.make_branch_and_tree('tree')
3275
self.build_tree_contents([('tree/file', LINES_ONE)])
3276
tree.add('file', 'file-id')
3277
return self.get_preview(tree), LINES_TWO
3279
def modification_records(self):
3280
attribs = self.default_attribs()
3281
attribs['_id_number'] = 2
3282
attribs['_tree_path_ids'] = {
3285
attribs['_removed_contents'] = ['new-1']
3286
contents = [('new-1', 'file',
3287
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3288
return self.make_records(attribs, contents)
3290
def test_serialize_modification(self):
3291
tt, LINES = self.make_modification_preview()
3292
trans_id = tt.trans_id_file_id('file-id')
3293
tt.delete_contents(trans_id)
3294
tt.create_file(LINES, trans_id)
3295
self.assertSerializesTo(self.modification_records(), tt)
3297
def test_deserialize_modification(self):
3298
tt, LINES = self.make_modification_preview()
3299
tt.deserialize(iter(self.modification_records()))
3300
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3302
def make_kind_change_preview(self):
3303
LINES = 'a\nb\nc\nd\n'
3304
tree = self.make_branch_and_tree('tree')
3305
self.build_tree(['tree/foo/'])
3306
tree.add('foo', 'foo-id')
3307
return self.get_preview(tree), LINES
3309
def kind_change_records(self):
3310
attribs = self.default_attribs()
3311
attribs['_id_number'] = 2
3312
attribs['_tree_path_ids'] = {
3315
attribs['_removed_contents'] = ['new-1']
3316
contents = [('new-1', 'file',
3317
'i 4\na\nb\nc\nd\n\n')]
3318
return self.make_records(attribs, contents)
3320
def test_serialize_kind_change(self):
3321
tt, LINES = self.make_kind_change_preview()
3322
trans_id = tt.trans_id_file_id('foo-id')
3323
tt.delete_contents(trans_id)
3324
tt.create_file(LINES, trans_id)
3325
self.assertSerializesTo(self.kind_change_records(), tt)
3327
def test_deserialize_kind_change(self):
3328
tt, LINES = self.make_kind_change_preview()
3329
tt.deserialize(iter(self.kind_change_records()))
3330
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3332
def make_add_contents_preview(self):
3333
LINES = 'a\nb\nc\nd\n'
3334
tree = self.make_branch_and_tree('tree')
3335
self.build_tree(['tree/foo'])
3337
os.unlink('tree/foo')
3338
return self.get_preview(tree), LINES
3340
def add_contents_records(self):
3341
attribs = self.default_attribs()
3342
attribs['_id_number'] = 2
3343
attribs['_tree_path_ids'] = {
3346
contents = [('new-1', 'file',
3347
'i 4\na\nb\nc\nd\n\n')]
3348
return self.make_records(attribs, contents)
3350
def test_serialize_add_contents(self):
3351
tt, LINES = self.make_add_contents_preview()
3352
trans_id = tt.trans_id_tree_path('foo')
3353
tt.create_file(LINES, trans_id)
3354
self.assertSerializesTo(self.add_contents_records(), tt)
3356
def test_deserialize_add_contents(self):
3357
tt, LINES = self.make_add_contents_preview()
3358
tt.deserialize(iter(self.add_contents_records()))
3359
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3361
def test_get_parents_lines(self):
3362
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3363
LINES_TWO = 'z\nbb\nx\ndd\n'
3364
tree = self.make_branch_and_tree('tree')
3365
self.build_tree_contents([('tree/file', LINES_ONE)])
3366
tree.add('file', 'file-id')
3367
tt = self.get_preview(tree)
3368
trans_id = tt.trans_id_tree_path('file')
3369
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3370
tt._get_parents_lines(trans_id))
3372
def test_get_parents_texts(self):
3373
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3374
LINES_TWO = 'z\nbb\nx\ndd\n'
3375
tree = self.make_branch_and_tree('tree')
3376
self.build_tree_contents([('tree/file', LINES_ONE)])
3377
tree.add('file', 'file-id')
3378
tt = self.get_preview(tree)
3379
trans_id = tt.trans_id_tree_path('file')
3380
self.assertEqual((LINES_ONE,),
3381
tt._get_parents_texts(trans_id))
3384
class TestOrphan(tests.TestCaseWithTransport):
3386
def test_no_orphan_for_transform_preview(self):
3387
tree = self.make_branch_and_tree('tree')
3388
tt = transform.TransformPreview(tree)
3389
self.addCleanup(tt.finalize)
3390
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3392
def _set_orphan_policy(self, wt, policy):
3393
wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
3396
def _prepare_orphan(self, wt):
3397
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3398
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3399
wt.commit('add dir and file ignoring foo')
3400
tt = transform.TreeTransform(wt)
3401
self.addCleanup(tt.finalize)
3402
# dir and bar are deleted
3403
dir_tid = tt.trans_id_tree_path('dir')
3404
file_tid = tt.trans_id_tree_path('dir/file')
3405
orphan_tid = tt.trans_id_tree_path('dir/foo')
3406
tt.delete_contents(file_tid)
3407
tt.unversion_file(file_tid)
3408
tt.delete_contents(dir_tid)
3409
tt.unversion_file(dir_tid)
3410
# There should be a conflict because dir still contain foo
3411
raw_conflicts = tt.find_conflicts()
3412
self.assertLength(1, raw_conflicts)
3413
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3414
return tt, orphan_tid
3416
def test_new_orphan_created(self):
3417
wt = self.make_branch_and_tree('.')
3418
self._set_orphan_policy(wt, 'move')
3419
tt, orphan_tid = self._prepare_orphan(wt)
3422
warnings.append(args[0] % args[1:])
3423
self.overrideAttr(trace, 'warning', warning)
3424
remaining_conflicts = resolve_conflicts(tt)
3425
self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
3427
# Yeah for resolved conflicts !
3428
self.assertLength(0, remaining_conflicts)
3429
# We have a new orphan
3430
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3431
self.assertEquals('bzr-orphans',
3432
tt.final_name(tt.final_parent(orphan_tid)))
3434
def test_never_orphan(self):
3435
wt = self.make_branch_and_tree('.')
3436
self._set_orphan_policy(wt, 'conflict')
3437
tt, orphan_tid = self._prepare_orphan(wt)
3438
remaining_conflicts = resolve_conflicts(tt)
3439
self.assertLength(1, remaining_conflicts)
3440
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3441
remaining_conflicts.pop())
3443
def test_orphan_error(self):
3444
def bogus_orphan(tt, orphan_id, parent_id):
3445
raise transform.OrphaningError(tt.final_name(orphan_id),
3446
tt.final_name(parent_id))
3447
transform.orphaning_registry.register('bogus', bogus_orphan,
3448
'Raise an error when orphaning')
3449
wt = self.make_branch_and_tree('.')
3450
self._set_orphan_policy(wt, 'bogus')
3451
tt, orphan_tid = self._prepare_orphan(wt)
3452
remaining_conflicts = resolve_conflicts(tt)
3453
self.assertLength(1, remaining_conflicts)
3454
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3455
remaining_conflicts.pop())
3457
def test_unknown_orphan_policy(self):
3458
wt = self.make_branch_and_tree('.')
3459
# Set a fictional policy nobody ever implemented
3460
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3461
tt, orphan_tid = self._prepare_orphan(wt)
3464
warnings.append(args[0] % args[1:])
3465
self.overrideAttr(trace, 'warning', warning)
3466
remaining_conflicts = resolve_conflicts(tt)
3467
# We fallback to the default policy which create a conflict
3468
self.assertLength(1, remaining_conflicts)
3469
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3470
remaining_conflicts.pop())
3471
self.assertLength(1, warnings)
3472
self.assertStartsWith(warnings[0], 'donttouchmypreciouuus')
1138
name = get_backup_name(MockEntry(), {'a':[]}, 'a', tt)
1139
self.assertEqual(name, 'name.~1~')
1140
name = get_backup_name(MockEntry(), {'a':['1']}, 'a', tt)
1141
self.assertEqual(name, 'name.~2~')
1142
name = get_backup_name(MockEntry(), {'a':['2']}, 'a', tt)
1143
self.assertEqual(name, 'name.~1~')
1144
name = get_backup_name(MockEntry(), {'a':['2'], 'b':[]}, 'b', tt)
1145
self.assertEqual(name, 'name.~1~')
1146
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1147
self.assertEqual(name, 'name.~4~')