1145
764
old = transform.trans_id_tree_path('old')
1146
765
subdir = transform.trans_id_tree_file_id('subdir-id')
1147
self.assertEqual([], list(transform.iter_changes()))
766
self.assertEqual([], list(transform._iter_changes()))
1148
767
transform.delete_contents(subdir)
1149
768
transform.create_directory(subdir)
1150
769
transform.set_executability(False, old)
1151
770
transform.unversion_file(old)
1152
771
transform.version_file('id-1', old)
1153
772
transform.adjust_path('old', root, old)
1154
self.assertEqual([], list(transform.iter_changes()))
1156
transform.finalize()
1158
def test_rename_count(self):
1159
transform, root = self.get_transform()
1160
transform.new_file('name1', root, 'contents')
1161
self.assertEqual(transform.rename_count, 0)
1163
self.assertEqual(transform.rename_count, 1)
1164
transform2, root = self.get_transform()
1165
transform2.adjust_path('name2', root,
1166
transform2.trans_id_tree_path('name1'))
1167
self.assertEqual(transform2.rename_count, 0)
1169
self.assertEqual(transform2.rename_count, 2)
1171
def test_change_parent(self):
1172
"""Ensure that after we change a parent, the results are still right.
1174
Renames and parent changes on pending transforms can happen as part
1175
of conflict resolution, and are explicitly permitted by the
1178
This test ensures they work correctly with the rename-avoidance
1181
transform, root = self.get_transform()
1182
parent1 = transform.new_directory('parent1', root)
1183
child1 = transform.new_file('child1', parent1, 'contents')
1184
parent2 = transform.new_directory('parent2', root)
1185
transform.adjust_path('child1', parent2, child1)
1187
self.failIfExists(self.wt.abspath('parent1/child1'))
1188
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1189
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1190
# no rename for child1 (counting only renames during apply)
1191
self.failUnlessEqual(2, transform.rename_count)
1193
def test_cancel_parent(self):
1194
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1196
This is like the test_change_parent, except that we cancel the parent
1197
before adjusting the path. The transform must detect that the
1198
directory is non-empty, and move children to safe locations.
1200
transform, root = self.get_transform()
1201
parent1 = transform.new_directory('parent1', root)
1202
child1 = transform.new_file('child1', parent1, 'contents')
1203
child2 = transform.new_file('child2', parent1, 'contents')
1205
transform.cancel_creation(parent1)
1207
self.fail('Failed to move child1 before deleting parent1')
1208
transform.cancel_creation(child2)
1209
transform.create_directory(parent1)
1211
transform.cancel_creation(parent1)
1212
# If the transform incorrectly believes that child2 is still in
1213
# parent1's limbo directory, it will try to rename it and fail
1214
# because was already moved by the first cancel_creation.
1216
self.fail('Transform still thinks child2 is a child of parent1')
1217
parent2 = transform.new_directory('parent2', root)
1218
transform.adjust_path('child1', parent2, child1)
1220
self.failIfExists(self.wt.abspath('parent1'))
1221
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1222
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1223
self.failUnlessEqual(2, transform.rename_count)
1225
def test_adjust_and_cancel(self):
1226
"""Make sure adjust_path keeps track of limbo children properly"""
1227
transform, root = self.get_transform()
1228
parent1 = transform.new_directory('parent1', root)
1229
child1 = transform.new_file('child1', parent1, 'contents')
1230
parent2 = transform.new_directory('parent2', root)
1231
transform.adjust_path('child1', parent2, child1)
1232
transform.cancel_creation(child1)
1234
transform.cancel_creation(parent1)
1235
# if the transform thinks child1 is still in parent1's limbo
1236
# directory, it will attempt to move it and fail.
1238
self.fail('Transform still thinks child1 is a child of parent1')
1239
transform.finalize()
1241
def test_noname_contents(self):
1242
"""TreeTransform should permit deferring naming files."""
1243
transform, root = self.get_transform()
1244
parent = transform.trans_id_file_id('parent-id')
1246
transform.create_directory(parent)
1248
self.fail("Can't handle contents with no name")
1249
transform.finalize()
1251
def test_noname_contents_nested(self):
1252
"""TreeTransform should permit deferring naming files."""
1253
transform, root = self.get_transform()
1254
parent = transform.trans_id_file_id('parent-id')
1256
transform.create_directory(parent)
1258
self.fail("Can't handle contents with no name")
1259
child = transform.new_directory('child', parent)
1260
transform.adjust_path('parent', root, parent)
1262
self.failUnlessExists(self.wt.abspath('parent/child'))
1263
self.assertEqual(1, transform.rename_count)
1265
def test_reuse_name(self):
1266
"""Avoid reusing the same limbo name for different files"""
1267
transform, root = self.get_transform()
1268
parent = transform.new_directory('parent', root)
1269
child1 = transform.new_directory('child', parent)
1271
child2 = transform.new_directory('child', parent)
1273
self.fail('Tranform tried to use the same limbo name twice')
1274
transform.adjust_path('child2', parent, child2)
1276
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1277
# child2 is put into top-level limbo because child1 has already
1278
# claimed the direct limbo path when child2 is created. There is no
1279
# advantage in renaming files once they're in top-level limbo, except
1281
self.assertEqual(2, transform.rename_count)
1283
def test_reuse_when_first_moved(self):
1284
"""Don't avoid direct paths when it is safe to use them"""
1285
transform, root = self.get_transform()
1286
parent = transform.new_directory('parent', root)
1287
child1 = transform.new_directory('child', parent)
1288
transform.adjust_path('child1', parent, child1)
1289
child2 = transform.new_directory('child', parent)
1291
# limbo/new-1 => parent
1292
self.assertEqual(1, transform.rename_count)
1294
def test_reuse_after_cancel(self):
1295
"""Don't avoid direct paths when it is safe to use them"""
1296
transform, root = self.get_transform()
1297
parent2 = transform.new_directory('parent2', root)
1298
child1 = transform.new_directory('child1', parent2)
1299
transform.cancel_creation(parent2)
1300
transform.create_directory(parent2)
1301
child2 = transform.new_directory('child1', parent2)
1302
transform.adjust_path('child2', parent2, child1)
1304
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1305
self.assertEqual(2, transform.rename_count)
1307
def test_finalize_order(self):
1308
"""Finalize must be done in child-to-parent order"""
1309
transform, root = self.get_transform()
1310
parent = transform.new_directory('parent', root)
1311
child = transform.new_directory('child', parent)
1313
transform.finalize()
1315
self.fail('Tried to remove parent before child1')
1317
def test_cancel_with_cancelled_child_should_succeed(self):
1318
transform, root = self.get_transform()
1319
parent = transform.new_directory('parent', root)
1320
child = transform.new_directory('child', parent)
1321
transform.cancel_creation(child)
1322
transform.cancel_creation(parent)
1323
transform.finalize()
1325
def test_rollback_on_directory_clash(self):
1327
wt = self.make_branch_and_tree('.')
1328
tt = TreeTransform(wt) # TreeTransform obtains write lock
1330
foo = tt.new_directory('foo', tt.root)
1331
tt.new_file('bar', foo, 'foobar')
1332
baz = tt.new_directory('baz', tt.root)
1333
tt.new_file('qux', baz, 'quux')
1334
# Ask for a rename 'foo' -> 'baz'
1335
tt.adjust_path('baz', tt.root, foo)
1336
# Lie to tt that we've already resolved all conflicts.
1337
tt.apply(no_conflicts=True)
1341
# The rename will fail because the target directory is not empty (but
1342
# raises FileExists anyway).
1343
err = self.assertRaises(errors.FileExists, tt_helper)
1344
self.assertContainsRe(str(err),
1345
"^File exists: .+/baz")
1347
def test_two_directories_clash(self):
1349
wt = self.make_branch_and_tree('.')
1350
tt = TreeTransform(wt) # TreeTransform obtains write lock
1352
foo_1 = tt.new_directory('foo', tt.root)
1353
tt.new_directory('bar', foo_1)
1354
# Adding the same directory with a different content
1355
foo_2 = tt.new_directory('foo', tt.root)
1356
tt.new_directory('baz', foo_2)
1357
# Lie to tt that we've already resolved all conflicts.
1358
tt.apply(no_conflicts=True)
1362
err = self.assertRaises(errors.FileExists, tt_helper)
1363
self.assertContainsRe(str(err),
1364
"^File exists: .+/foo")
1366
def test_two_directories_clash_finalize(self):
1368
wt = self.make_branch_and_tree('.')
1369
tt = TreeTransform(wt) # TreeTransform obtains write lock
1371
foo_1 = tt.new_directory('foo', tt.root)
1372
tt.new_directory('bar', foo_1)
1373
# Adding the same directory with a different content
1374
foo_2 = tt.new_directory('foo', tt.root)
1375
tt.new_directory('baz', foo_2)
1376
# Lie to tt that we've already resolved all conflicts.
1377
tt.apply(no_conflicts=True)
1381
err = self.assertRaises(errors.FileExists, tt_helper)
1382
self.assertContainsRe(str(err),
1383
"^File exists: .+/foo")
1385
def test_file_to_directory(self):
1386
wt = self.make_branch_and_tree('.')
1387
self.build_tree(['foo'])
1390
tt = TreeTransform(wt)
1391
self.addCleanup(tt.finalize)
1392
foo_trans_id = tt.trans_id_tree_path("foo")
1393
tt.delete_contents(foo_trans_id)
1394
tt.create_directory(foo_trans_id)
1395
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1396
tt.create_file(["aa\n"], bar_trans_id)
1397
tt.version_file("bar-1", bar_trans_id)
1399
self.failUnlessExists("foo/bar")
1402
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1407
changes = wt.changes_from(wt.basis_tree())
1408
self.assertFalse(changes.has_changed(), changes)
1410
def test_file_to_symlink(self):
1411
self.requireFeature(SymlinkFeature)
1412
wt = self.make_branch_and_tree('.')
1413
self.build_tree(['foo'])
1416
tt = TreeTransform(wt)
1417
self.addCleanup(tt.finalize)
1418
foo_trans_id = tt.trans_id_tree_path("foo")
1419
tt.delete_contents(foo_trans_id)
1420
tt.create_symlink("bar", foo_trans_id)
1422
self.failUnlessExists("foo")
1424
self.addCleanup(wt.unlock)
1425
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1428
def test_dir_to_file(self):
1429
wt = self.make_branch_and_tree('.')
1430
self.build_tree(['foo/', 'foo/bar'])
1431
wt.add(['foo', 'foo/bar'])
1433
tt = TreeTransform(wt)
1434
self.addCleanup(tt.finalize)
1435
foo_trans_id = tt.trans_id_tree_path("foo")
1436
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1437
tt.delete_contents(foo_trans_id)
1438
tt.delete_versioned(bar_trans_id)
1439
tt.create_file(["aa\n"], foo_trans_id)
1441
self.failUnlessExists("foo")
1443
self.addCleanup(wt.unlock)
1444
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1447
def test_dir_to_hardlink(self):
1448
self.requireFeature(HardlinkFeature)
1449
wt = self.make_branch_and_tree('.')
1450
self.build_tree(['foo/', 'foo/bar'])
1451
wt.add(['foo', 'foo/bar'])
1453
tt = TreeTransform(wt)
1454
self.addCleanup(tt.finalize)
1455
foo_trans_id = tt.trans_id_tree_path("foo")
1456
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1457
tt.delete_contents(foo_trans_id)
1458
tt.delete_versioned(bar_trans_id)
1459
self.build_tree(['baz'])
1460
tt.create_hardlink("baz", foo_trans_id)
1462
self.failUnlessExists("foo")
1463
self.failUnlessExists("baz")
1465
self.addCleanup(wt.unlock)
1466
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1469
def test_no_final_path(self):
1470
transform, root = self.get_transform()
1471
trans_id = transform.trans_id_file_id('foo')
1472
transform.create_file('bar', trans_id)
1473
transform.cancel_creation(trans_id)
1476
def test_create_from_tree(self):
1477
tree1 = self.make_branch_and_tree('tree1')
1478
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1479
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1480
tree2 = self.make_branch_and_tree('tree2')
1481
tt = TreeTransform(tree2)
1482
foo_trans_id = tt.create_path('foo', tt.root)
1483
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1484
bar_trans_id = tt.create_path('bar', tt.root)
1485
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1487
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1488
self.assertFileEqual('baz', 'tree2/bar')
1490
def test_create_from_tree_bytes(self):
1491
"""Provided lines are used instead of tree content."""
1492
tree1 = self.make_branch_and_tree('tree1')
1493
self.build_tree_contents([('tree1/foo', 'bar'),])
1494
tree1.add('foo', 'foo-id')
1495
tree2 = self.make_branch_and_tree('tree2')
1496
tt = TreeTransform(tree2)
1497
foo_trans_id = tt.create_path('foo', tt.root)
1498
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1500
self.assertFileEqual('qux', 'tree2/foo')
1502
def test_create_from_tree_symlink(self):
1503
self.requireFeature(SymlinkFeature)
1504
tree1 = self.make_branch_and_tree('tree1')
1505
os.symlink('bar', 'tree1/foo')
1506
tree1.add('foo', 'foo-id')
1507
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1508
foo_trans_id = tt.create_path('foo', tt.root)
1509
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1511
self.assertEqual('bar', os.readlink('tree2/foo'))
773
self.assertEqual([], list(transform._iter_changes()))
1514
777
class TransformGroup(object):
1516
778
def __init__(self, dirname, root_id):
1517
779
self.name = dirname
1518
780
os.mkdir(dirname)
1849
1112
target = self.make_branch_and_tree('target')
1850
1113
self.build_tree(['target/name'])
1851
1114
target.add('name')
1852
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1115
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1853
1116
build_tree, source.basis_tree(), target)
1855
def test_build_tree_rename_count(self):
1856
source = self.make_branch_and_tree('source')
1857
self.build_tree(['source/file1', 'source/dir1/'])
1858
source.add(['file1', 'dir1'])
1859
source.commit('add1')
1860
target1 = self.make_branch_and_tree('target1')
1861
transform_result = build_tree(source.basis_tree(), target1)
1862
self.assertEqual(2, transform_result.rename_count)
1864
self.build_tree(['source/dir1/file2'])
1865
source.add(['dir1/file2'])
1866
source.commit('add3')
1867
target2 = self.make_branch_and_tree('target2')
1868
transform_result = build_tree(source.basis_tree(), target2)
1869
# children of non-root directories should not be renamed
1870
self.assertEqual(2, transform_result.rename_count)
1872
def create_ab_tree(self):
1873
"""Create a committed test tree with two files"""
1874
source = self.make_branch_and_tree('source')
1875
self.build_tree_contents([('source/file1', 'A')])
1876
self.build_tree_contents([('source/file2', 'B')])
1877
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1878
source.commit('commit files')
1880
self.addCleanup(source.unlock)
1883
def test_build_tree_accelerator_tree(self):
1884
source = self.create_ab_tree()
1885
self.build_tree_contents([('source/file2', 'C')])
1887
real_source_get_file = source.get_file
1888
def get_file(file_id, path=None):
1889
calls.append(file_id)
1890
return real_source_get_file(file_id, path)
1891
source.get_file = get_file
1892
target = self.make_branch_and_tree('target')
1893
revision_tree = source.basis_tree()
1894
revision_tree.lock_read()
1895
self.addCleanup(revision_tree.unlock)
1896
build_tree(revision_tree, target, source)
1897
self.assertEqual(['file1-id'], calls)
1899
self.addCleanup(target.unlock)
1900
self.assertEqual([], list(target.iter_changes(revision_tree)))
1902
def test_build_tree_accelerator_tree_missing_file(self):
1903
source = self.create_ab_tree()
1904
os.unlink('source/file1')
1905
source.remove(['file2'])
1906
target = self.make_branch_and_tree('target')
1907
revision_tree = source.basis_tree()
1908
revision_tree.lock_read()
1909
self.addCleanup(revision_tree.unlock)
1910
build_tree(revision_tree, target, source)
1912
self.addCleanup(target.unlock)
1913
self.assertEqual([], list(target.iter_changes(revision_tree)))
1915
def test_build_tree_accelerator_wrong_kind(self):
1916
self.requireFeature(SymlinkFeature)
1917
source = self.make_branch_and_tree('source')
1918
self.build_tree_contents([('source/file1', '')])
1919
self.build_tree_contents([('source/file2', '')])
1920
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1921
source.commit('commit files')
1922
os.unlink('source/file2')
1923
self.build_tree_contents([('source/file2/', 'C')])
1924
os.unlink('source/file1')
1925
os.symlink('file2', 'source/file1')
1927
real_source_get_file = source.get_file
1928
def get_file(file_id, path=None):
1929
calls.append(file_id)
1930
return real_source_get_file(file_id, path)
1931
source.get_file = get_file
1932
target = self.make_branch_and_tree('target')
1933
revision_tree = source.basis_tree()
1934
revision_tree.lock_read()
1935
self.addCleanup(revision_tree.unlock)
1936
build_tree(revision_tree, target, source)
1937
self.assertEqual([], calls)
1939
self.addCleanup(target.unlock)
1940
self.assertEqual([], list(target.iter_changes(revision_tree)))
1942
def test_build_tree_hardlink(self):
1943
self.requireFeature(HardlinkFeature)
1944
source = self.create_ab_tree()
1945
target = self.make_branch_and_tree('target')
1946
revision_tree = source.basis_tree()
1947
revision_tree.lock_read()
1948
self.addCleanup(revision_tree.unlock)
1949
build_tree(revision_tree, target, source, hardlink=True)
1951
self.addCleanup(target.unlock)
1952
self.assertEqual([], list(target.iter_changes(revision_tree)))
1953
source_stat = os.stat('source/file1')
1954
target_stat = os.stat('target/file1')
1955
self.assertEqual(source_stat, target_stat)
1957
# Explicitly disallowing hardlinks should prevent them.
1958
target2 = self.make_branch_and_tree('target2')
1959
build_tree(revision_tree, target2, source, hardlink=False)
1961
self.addCleanup(target2.unlock)
1962
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1963
source_stat = os.stat('source/file1')
1964
target2_stat = os.stat('target2/file1')
1965
self.assertNotEqual(source_stat, target2_stat)
1967
def test_build_tree_accelerator_tree_moved(self):
1968
source = self.make_branch_and_tree('source')
1969
self.build_tree_contents([('source/file1', 'A')])
1970
source.add(['file1'], ['file1-id'])
1971
source.commit('commit files')
1972
source.rename_one('file1', 'file2')
1974
self.addCleanup(source.unlock)
1975
target = self.make_branch_and_tree('target')
1976
revision_tree = source.basis_tree()
1977
revision_tree.lock_read()
1978
self.addCleanup(revision_tree.unlock)
1979
build_tree(revision_tree, target, source)
1981
self.addCleanup(target.unlock)
1982
self.assertEqual([], list(target.iter_changes(revision_tree)))
1984
def test_build_tree_hardlinks_preserve_execute(self):
1985
self.requireFeature(HardlinkFeature)
1986
source = self.create_ab_tree()
1987
tt = TreeTransform(source)
1988
trans_id = tt.trans_id_tree_file_id('file1-id')
1989
tt.set_executability(True, trans_id)
1991
self.assertTrue(source.is_executable('file1-id'))
1992
target = self.make_branch_and_tree('target')
1993
revision_tree = source.basis_tree()
1994
revision_tree.lock_read()
1995
self.addCleanup(revision_tree.unlock)
1996
build_tree(revision_tree, target, source, hardlink=True)
1998
self.addCleanup(target.unlock)
1999
self.assertEqual([], list(target.iter_changes(revision_tree)))
2000
self.assertTrue(source.is_executable('file1-id'))
2002
def install_rot13_content_filter(self, pattern):
2004
# self.addCleanup(filters._reset_registry, filters._reset_registry())
2005
# below, but that looks a bit... hard to read even if it's exactly
2007
original_registry = filters._reset_registry()
2008
def restore_registry():
2009
filters._reset_registry(original_registry)
2010
self.addCleanup(restore_registry)
2011
def rot13(chunks, context=None):
2012
return [''.join(chunks).encode('rot13')]
2013
rot13filter = filters.ContentFilter(rot13, rot13)
2014
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
2015
os.mkdir(self.test_home_dir + '/.bazaar')
2016
rules_filename = self.test_home_dir + '/.bazaar/rules'
2017
f = open(rules_filename, 'wb')
2018
f.write('[name %s]\nrot13=yes\n' % (pattern,))
2020
def uninstall_rules():
2021
os.remove(rules_filename)
2023
self.addCleanup(uninstall_rules)
2026
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
2027
"""build_tree will not hardlink files that have content filtering rules
2028
applied to them (but will still hardlink other files from the same tree
2031
self.requireFeature(HardlinkFeature)
2032
self.install_rot13_content_filter('file1')
2033
source = self.create_ab_tree()
2034
target = self.make_branch_and_tree('target')
2035
revision_tree = source.basis_tree()
2036
revision_tree.lock_read()
2037
self.addCleanup(revision_tree.unlock)
2038
build_tree(revision_tree, target, source, hardlink=True)
2040
self.addCleanup(target.unlock)
2041
self.assertEqual([], list(target.iter_changes(revision_tree)))
2042
source_stat = os.stat('source/file1')
2043
target_stat = os.stat('target/file1')
2044
self.assertNotEqual(source_stat, target_stat)
2045
source_stat = os.stat('source/file2')
2046
target_stat = os.stat('target/file2')
2047
self.assertEqualStat(source_stat, target_stat)
2049
def test_case_insensitive_build_tree_inventory(self):
2050
if (tests.CaseInsensitiveFilesystemFeature.available()
2051
or tests.CaseInsCasePresFilenameFeature.available()):
2052
raise tests.UnavailableFeature('Fully case sensitive filesystem')
2053
source = self.make_branch_and_tree('source')
2054
self.build_tree(['source/file', 'source/FILE'])
2055
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2056
source.commit('added files')
2057
# Don't try this at home, kids!
2058
# Force the tree to report that it is case insensitive
2059
target = self.make_branch_and_tree('target')
2060
target.case_sensitive = False
2061
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2062
self.assertEqual('file.moved', target.id2path('lower-id'))
2063
self.assertEqual('FILE', target.id2path('upper-id'))
2066
class TestCommitTransform(tests.TestCaseWithTransport):
2068
def get_branch(self):
2069
tree = self.make_branch_and_tree('tree')
2071
self.addCleanup(tree.unlock)
2072
tree.commit('empty commit')
2075
def get_branch_and_transform(self):
2076
branch = self.get_branch()
2077
tt = TransformPreview(branch.basis_tree())
2078
self.addCleanup(tt.finalize)
2081
def test_commit_wrong_basis(self):
2082
branch = self.get_branch()
2083
basis = branch.repository.revision_tree(
2084
_mod_revision.NULL_REVISION)
2085
tt = TransformPreview(basis)
2086
self.addCleanup(tt.finalize)
2087
e = self.assertRaises(ValueError, tt.commit, branch, '')
2088
self.assertEqual('TreeTransform not based on branch basis: null:',
2091
def test_empy_commit(self):
2092
branch, tt = self.get_branch_and_transform()
2093
rev = tt.commit(branch, 'my message')
2094
self.assertEqual(2, branch.revno())
2095
repo = branch.repository
2096
self.assertEqual('my message', repo.get_revision(rev).message)
2098
def test_merge_parents(self):
2099
branch, tt = self.get_branch_and_transform()
2100
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2101
self.assertEqual(['rev1b', 'rev1c'],
2102
branch.basis_tree().get_parent_ids()[1:])
2104
def test_first_commit(self):
2105
branch = self.make_branch('branch')
2107
self.addCleanup(branch.unlock)
2108
tt = TransformPreview(branch.basis_tree())
2109
self.addCleanup(tt.finalize)
2110
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2111
rev = tt.commit(branch, 'my message')
2112
self.assertEqual([], branch.basis_tree().get_parent_ids())
2113
self.assertNotEqual(_mod_revision.NULL_REVISION,
2114
branch.last_revision())
2116
def test_first_commit_with_merge_parents(self):
2117
branch = self.make_branch('branch')
2119
self.addCleanup(branch.unlock)
2120
tt = TransformPreview(branch.basis_tree())
2121
self.addCleanup(tt.finalize)
2122
e = self.assertRaises(ValueError, tt.commit, branch,
2123
'my message', ['rev1b-id'])
2124
self.assertEqual('Cannot supply merge parents for first commit.',
2126
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2128
def test_add_files(self):
2129
branch, tt = self.get_branch_and_transform()
2130
tt.new_file('file', tt.root, 'contents', 'file-id')
2131
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2132
if SymlinkFeature.available():
2133
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2134
rev = tt.commit(branch, 'message')
2135
tree = branch.basis_tree()
2136
self.assertEqual('file', tree.id2path('file-id'))
2137
self.assertEqual('contents', tree.get_file_text('file-id'))
2138
self.assertEqual('dir', tree.id2path('dir-id'))
2139
if SymlinkFeature.available():
2140
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2141
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2143
def test_add_unversioned(self):
2144
branch, tt = self.get_branch_and_transform()
2145
tt.new_file('file', tt.root, 'contents')
2146
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2147
'message', strict=True)
2149
def test_modify_strict(self):
2150
branch, tt = self.get_branch_and_transform()
2151
tt.new_file('file', tt.root, 'contents', 'file-id')
2152
tt.commit(branch, 'message', strict=True)
2153
tt = TransformPreview(branch.basis_tree())
2154
self.addCleanup(tt.finalize)
2155
trans_id = tt.trans_id_file_id('file-id')
2156
tt.delete_contents(trans_id)
2157
tt.create_file('contents', trans_id)
2158
tt.commit(branch, 'message', strict=True)
2160
def test_commit_malformed(self):
2161
"""Committing a malformed transform should raise an exception.
2163
In this case, we are adding a file without adding its parent.
2165
branch, tt = self.get_branch_and_transform()
2166
parent_id = tt.trans_id_file_id('parent-id')
2167
tt.new_file('file', parent_id, 'contents', 'file-id')
2168
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2171
def test_commit_rich_revision_data(self):
2172
branch, tt = self.get_branch_and_transform()
2173
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2174
committer='me <me@example.com>',
2175
revprops={'foo': 'bar'}, revision_id='revid-1',
2176
authors=['Author1 <author1@example.com>',
2177
'Author2 <author2@example.com>',
2179
self.assertEqual('revid-1', rev_id)
2180
revision = branch.repository.get_revision(rev_id)
2181
self.assertEqual(1, revision.timestamp)
2182
self.assertEqual(43201, revision.timezone)
2183
self.assertEqual('me <me@example.com>', revision.committer)
2184
self.assertEqual(['Author1 <author1@example.com>',
2185
'Author2 <author2@example.com>'],
2186
revision.get_apparent_authors())
2187
del revision.properties['authors']
2188
self.assertEqual({'foo': 'bar',
2189
'branch-nick': 'tree'},
2190
revision.properties)
2192
def test_no_explicit_revprops(self):
2193
branch, tt = self.get_branch_and_transform()
2194
rev_id = tt.commit(branch, 'message', authors=[
2195
'Author1 <author1@example.com>',
2196
'Author2 <author2@example.com>', ])
2197
revision = branch.repository.get_revision(rev_id)
2198
self.assertEqual(['Author1 <author1@example.com>',
2199
'Author2 <author2@example.com>'],
2200
revision.get_apparent_authors())
2201
self.assertEqual('tree', revision.properties['branch-nick'])
2204
class TestBackupName(tests.TestCase):
2206
def test_deprecations(self):
2207
class MockTransform(object):
2209
def has_named_child(self, by_parent, parent_id, name):
2210
return name in by_parent.get(parent_id, [])
2212
class MockEntry(object):
2215
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):
2218
1137
tt = MockTransform()
2219
name1 = self.applyDeprecated(
2220
symbol_versioning.deprecated_in((2, 3, 0)),
2221
transform.get_backup_name, MockEntry(), {'a':[]}, 'a', tt)
2222
self.assertEqual('name.~1~', name1)
2223
name2 = self.applyDeprecated(
2224
symbol_versioning.deprecated_in((2, 3, 0)),
2225
transform._get_backup_name, 'name', {'a':['name.~1~']}, 'a', tt)
2226
self.assertEqual('name.~2~', name2)
2229
class TestFileMover(tests.TestCaseWithTransport):
2231
def test_file_mover(self):
2232
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2233
mover = _FileMover()
2234
mover.rename('a', 'q')
2235
self.failUnlessExists('q')
2236
self.failIfExists('a')
2237
self.failUnlessExists('q/b')
2238
self.failUnlessExists('c')
2239
self.failUnlessExists('c/d')
2241
def test_pre_delete_rollback(self):
2242
self.build_tree(['a/'])
2243
mover = _FileMover()
2244
mover.pre_delete('a', 'q')
2245
self.failUnlessExists('q')
2246
self.failIfExists('a')
2248
self.failIfExists('q')
2249
self.failUnlessExists('a')
2251
def test_apply_deletions(self):
2252
self.build_tree(['a/', 'b/'])
2253
mover = _FileMover()
2254
mover.pre_delete('a', 'q')
2255
mover.pre_delete('b', 'r')
2256
self.failUnlessExists('q')
2257
self.failUnlessExists('r')
2258
self.failIfExists('a')
2259
self.failIfExists('b')
2260
mover.apply_deletions()
2261
self.failIfExists('q')
2262
self.failIfExists('r')
2263
self.failIfExists('a')
2264
self.failIfExists('b')
2266
def test_file_mover_rollback(self):
2267
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2268
mover = _FileMover()
2269
mover.rename('c/d', 'c/f')
2270
mover.rename('c/e', 'c/d')
2272
mover.rename('a', 'c')
2273
except errors.FileExists, e:
2275
self.failUnlessExists('a')
2276
self.failUnlessExists('c/d')
2279
class Bogus(Exception):
2283
class TestTransformRollback(tests.TestCaseWithTransport):
2285
class ExceptionFileMover(_FileMover):
2287
def __init__(self, bad_source=None, bad_target=None):
2288
_FileMover.__init__(self)
2289
self.bad_source = bad_source
2290
self.bad_target = bad_target
2292
def rename(self, source, target):
2293
if (self.bad_source is not None and
2294
source.endswith(self.bad_source)):
2296
elif (self.bad_target is not None and
2297
target.endswith(self.bad_target)):
2300
_FileMover.rename(self, source, target)
2302
def test_rollback_rename(self):
2303
tree = self.make_branch_and_tree('.')
2304
self.build_tree(['a/', 'a/b'])
2305
tt = TreeTransform(tree)
2306
self.addCleanup(tt.finalize)
2307
a_id = tt.trans_id_tree_path('a')
2308
tt.adjust_path('c', tt.root, a_id)
2309
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2310
self.assertRaises(Bogus, tt.apply,
2311
_mover=self.ExceptionFileMover(bad_source='a'))
2312
self.failUnlessExists('a')
2313
self.failUnlessExists('a/b')
2315
self.failUnlessExists('c')
2316
self.failUnlessExists('c/d')
2318
def test_rollback_rename_into_place(self):
2319
tree = self.make_branch_and_tree('.')
2320
self.build_tree(['a/', 'a/b'])
2321
tt = TreeTransform(tree)
2322
self.addCleanup(tt.finalize)
2323
a_id = tt.trans_id_tree_path('a')
2324
tt.adjust_path('c', tt.root, a_id)
2325
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2326
self.assertRaises(Bogus, tt.apply,
2327
_mover=self.ExceptionFileMover(bad_target='c/d'))
2328
self.failUnlessExists('a')
2329
self.failUnlessExists('a/b')
2331
self.failUnlessExists('c')
2332
self.failUnlessExists('c/d')
2334
def test_rollback_deletion(self):
2335
tree = self.make_branch_and_tree('.')
2336
self.build_tree(['a/', 'a/b'])
2337
tt = TreeTransform(tree)
2338
self.addCleanup(tt.finalize)
2339
a_id = tt.trans_id_tree_path('a')
2340
tt.delete_contents(a_id)
2341
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2342
self.assertRaises(Bogus, tt.apply,
2343
_mover=self.ExceptionFileMover(bad_target='d'))
2344
self.failUnlessExists('a')
2345
self.failUnlessExists('a/b')
2348
class TestTransformMissingParent(tests.TestCaseWithTransport):
2350
def make_tt_with_versioned_dir(self):
2351
wt = self.make_branch_and_tree('.')
2352
self.build_tree(['dir/',])
2353
wt.add(['dir'], ['dir-id'])
2354
wt.commit('Create dir')
2355
tt = TreeTransform(wt)
2356
self.addCleanup(tt.finalize)
2359
def test_resolve_create_parent_for_versioned_file(self):
2360
wt, tt = self.make_tt_with_versioned_dir()
2361
dir_tid = tt.trans_id_tree_file_id('dir-id')
2362
file_tid = tt.new_file('file', dir_tid, 'Contents', file_id='file-id')
2363
tt.delete_contents(dir_tid)
2364
tt.unversion_file(dir_tid)
2365
conflicts = resolve_conflicts(tt)
2366
# one conflict for the missing directory, one for the unversioned
2368
self.assertLength(2, conflicts)
2370
def test_non_versioned_file_create_conflict(self):
2371
wt, tt = self.make_tt_with_versioned_dir()
2372
dir_tid = tt.trans_id_tree_file_id('dir-id')
2373
tt.new_file('file', dir_tid, 'Contents')
2374
tt.delete_contents(dir_tid)
2375
tt.unversion_file(dir_tid)
2376
conflicts = resolve_conflicts(tt)
2377
# no conflicts or rather: orphaning 'file' resolve the 'dir' conflict
2378
self.assertLength(1, conflicts)
2379
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
2383
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2384
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2386
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2387
('', ''), ('directory', 'directory'), (False, False))
2390
class TestTransformPreview(tests.TestCaseWithTransport):
2392
def create_tree(self):
2393
tree = self.make_branch_and_tree('.')
2394
self.build_tree_contents([('a', 'content 1')])
2395
tree.set_root_id('TREE_ROOT')
2396
tree.add('a', 'a-id')
2397
tree.commit('rev1', rev_id='rev1')
2398
return tree.branch.repository.revision_tree('rev1')
2400
def get_empty_preview(self):
2401
repository = self.make_repository('repo')
2402
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2403
preview = TransformPreview(tree)
2404
self.addCleanup(preview.finalize)
2407
def test_transform_preview(self):
2408
revision_tree = self.create_tree()
2409
preview = TransformPreview(revision_tree)
2410
self.addCleanup(preview.finalize)
2412
def test_transform_preview_tree(self):
2413
revision_tree = self.create_tree()
2414
preview = TransformPreview(revision_tree)
2415
self.addCleanup(preview.finalize)
2416
preview.get_preview_tree()
2418
def test_transform_new_file(self):
2419
revision_tree = self.create_tree()
2420
preview = TransformPreview(revision_tree)
2421
self.addCleanup(preview.finalize)
2422
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2423
preview_tree = preview.get_preview_tree()
2424
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2426
preview_tree.get_file('file2-id').read(), 'content B\n')
2428
def test_diff_preview_tree(self):
2429
revision_tree = self.create_tree()
2430
preview = TransformPreview(revision_tree)
2431
self.addCleanup(preview.finalize)
2432
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2433
preview_tree = preview.get_preview_tree()
2435
show_diff_trees(revision_tree, preview_tree, out)
2436
lines = out.getvalue().splitlines()
2437
self.assertEqual(lines[0], "=== added file 'file2'")
2438
# 3 lines of diff administrivia
2439
self.assertEqual(lines[4], "+content B")
2441
def test_transform_conflicts(self):
2442
revision_tree = self.create_tree()
2443
preview = TransformPreview(revision_tree)
2444
self.addCleanup(preview.finalize)
2445
preview.new_file('a', preview.root, 'content 2')
2446
resolve_conflicts(preview)
2447
trans_id = preview.trans_id_file_id('a-id')
2448
self.assertEqual('a.moved', preview.final_name(trans_id))
2450
def get_tree_and_preview_tree(self):
2451
revision_tree = self.create_tree()
2452
preview = TransformPreview(revision_tree)
2453
self.addCleanup(preview.finalize)
2454
a_trans_id = preview.trans_id_file_id('a-id')
2455
preview.delete_contents(a_trans_id)
2456
preview.create_file('b content', a_trans_id)
2457
preview_tree = preview.get_preview_tree()
2458
return revision_tree, preview_tree
2460
def test_iter_changes(self):
2461
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2462
root = revision_tree.inventory.root.file_id
2463
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2464
(root, root), ('a', 'a'), ('file', 'file'),
2466
list(preview_tree.iter_changes(revision_tree)))
2468
def test_include_unchanged_succeeds(self):
2469
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2470
changes = preview_tree.iter_changes(revision_tree,
2471
include_unchanged=True)
2472
root = revision_tree.inventory.root.file_id
2474
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2476
def test_specific_files(self):
2477
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2478
changes = preview_tree.iter_changes(revision_tree,
2479
specific_files=[''])
2480
self.assertEqual([A_ENTRY], list(changes))
2482
def test_want_unversioned(self):
2483
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2484
changes = preview_tree.iter_changes(revision_tree,
2485
want_unversioned=True)
2486
self.assertEqual([A_ENTRY], list(changes))
2488
def test_ignore_extra_trees_no_specific_files(self):
2489
# extra_trees is harmless without specific_files, so we'll silently
2490
# accept it, even though we won't use it.
2491
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2492
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2494
def test_ignore_require_versioned_no_specific_files(self):
2495
# require_versioned is meaningless without specific_files.
2496
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2497
preview_tree.iter_changes(revision_tree, require_versioned=False)
2499
def test_ignore_pb(self):
2500
# pb could be supported, but TT.iter_changes doesn't support it.
2501
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2502
preview_tree.iter_changes(revision_tree)
2504
def test_kind(self):
2505
revision_tree = self.create_tree()
2506
preview = TransformPreview(revision_tree)
2507
self.addCleanup(preview.finalize)
2508
preview.new_file('file', preview.root, 'contents', 'file-id')
2509
preview.new_directory('directory', preview.root, 'dir-id')
2510
preview_tree = preview.get_preview_tree()
2511
self.assertEqual('file', preview_tree.kind('file-id'))
2512
self.assertEqual('directory', preview_tree.kind('dir-id'))
2514
def test_get_file_mtime(self):
2515
preview = self.get_empty_preview()
2516
file_trans_id = preview.new_file('file', preview.root, 'contents',
2518
limbo_path = preview._limbo_name(file_trans_id)
2519
preview_tree = preview.get_preview_tree()
2520
self.assertEqual(os.stat(limbo_path).st_mtime,
2521
preview_tree.get_file_mtime('file-id'))
2523
def test_get_file_mtime_renamed(self):
2524
work_tree = self.make_branch_and_tree('tree')
2525
self.build_tree(['tree/file'])
2526
work_tree.add('file', 'file-id')
2527
preview = TransformPreview(work_tree)
2528
self.addCleanup(preview.finalize)
2529
file_trans_id = preview.trans_id_tree_file_id('file-id')
2530
preview.adjust_path('renamed', preview.root, file_trans_id)
2531
preview_tree = preview.get_preview_tree()
2532
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2533
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2535
def test_get_file(self):
2536
preview = self.get_empty_preview()
2537
preview.new_file('file', preview.root, 'contents', 'file-id')
2538
preview_tree = preview.get_preview_tree()
2539
tree_file = preview_tree.get_file('file-id')
2541
self.assertEqual('contents', tree_file.read())
2545
def test_get_symlink_target(self):
2546
self.requireFeature(SymlinkFeature)
2547
preview = self.get_empty_preview()
2548
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2549
preview_tree = preview.get_preview_tree()
2550
self.assertEqual('target',
2551
preview_tree.get_symlink_target('symlink-id'))
2553
def test_all_file_ids(self):
2554
tree = self.make_branch_and_tree('tree')
2555
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2556
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2557
preview = TransformPreview(tree)
2558
self.addCleanup(preview.finalize)
2559
preview.unversion_file(preview.trans_id_file_id('b-id'))
2560
c_trans_id = preview.trans_id_file_id('c-id')
2561
preview.unversion_file(c_trans_id)
2562
preview.version_file('c-id', c_trans_id)
2563
preview_tree = preview.get_preview_tree()
2564
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2565
preview_tree.all_file_ids())
2567
def test_path2id_deleted_unchanged(self):
2568
tree = self.make_branch_and_tree('tree')
2569
self.build_tree(['tree/unchanged', 'tree/deleted'])
2570
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2571
preview = TransformPreview(tree)
2572
self.addCleanup(preview.finalize)
2573
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2574
preview_tree = preview.get_preview_tree()
2575
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2576
self.assertIs(None, preview_tree.path2id('deleted'))
2578
def test_path2id_created(self):
2579
tree = self.make_branch_and_tree('tree')
2580
self.build_tree(['tree/unchanged'])
2581
tree.add(['unchanged'], ['unchanged-id'])
2582
preview = TransformPreview(tree)
2583
self.addCleanup(preview.finalize)
2584
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2585
'contents', 'new-id')
2586
preview_tree = preview.get_preview_tree()
2587
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2589
def test_path2id_moved(self):
2590
tree = self.make_branch_and_tree('tree')
2591
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2592
tree.add(['old_parent', 'old_parent/child'],
2593
['old_parent-id', 'child-id'])
2594
preview = TransformPreview(tree)
2595
self.addCleanup(preview.finalize)
2596
new_parent = preview.new_directory('new_parent', preview.root,
2598
preview.adjust_path('child', new_parent,
2599
preview.trans_id_file_id('child-id'))
2600
preview_tree = preview.get_preview_tree()
2601
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2602
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2604
def test_path2id_renamed_parent(self):
2605
tree = self.make_branch_and_tree('tree')
2606
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2607
tree.add(['old_name', 'old_name/child'],
2608
['parent-id', 'child-id'])
2609
preview = TransformPreview(tree)
2610
self.addCleanup(preview.finalize)
2611
preview.adjust_path('new_name', preview.root,
2612
preview.trans_id_file_id('parent-id'))
2613
preview_tree = preview.get_preview_tree()
2614
self.assertIs(None, preview_tree.path2id('old_name/child'))
2615
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2617
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2618
preview_tree = tt.get_preview_tree()
2619
preview_result = list(preview_tree.iter_entries_by_dir(
2623
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2624
self.assertEqual(actual_result, preview_result)
2626
def test_iter_entries_by_dir_new(self):
2627
tree = self.make_branch_and_tree('tree')
2628
tt = TreeTransform(tree)
2629
tt.new_file('new', tt.root, 'contents', 'new-id')
2630
self.assertMatchingIterEntries(tt)
2632
def test_iter_entries_by_dir_deleted(self):
2633
tree = self.make_branch_and_tree('tree')
2634
self.build_tree(['tree/deleted'])
2635
tree.add('deleted', 'deleted-id')
2636
tt = TreeTransform(tree)
2637
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2638
self.assertMatchingIterEntries(tt)
2640
def test_iter_entries_by_dir_unversioned(self):
2641
tree = self.make_branch_and_tree('tree')
2642
self.build_tree(['tree/removed'])
2643
tree.add('removed', 'removed-id')
2644
tt = TreeTransform(tree)
2645
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2646
self.assertMatchingIterEntries(tt)
2648
def test_iter_entries_by_dir_moved(self):
2649
tree = self.make_branch_and_tree('tree')
2650
self.build_tree(['tree/moved', 'tree/new_parent/'])
2651
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2652
tt = TreeTransform(tree)
2653
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2654
tt.trans_id_file_id('moved-id'))
2655
self.assertMatchingIterEntries(tt)
2657
def test_iter_entries_by_dir_specific_file_ids(self):
2658
tree = self.make_branch_and_tree('tree')
2659
tree.set_root_id('tree-root-id')
2660
self.build_tree(['tree/parent/', 'tree/parent/child'])
2661
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2662
tt = TreeTransform(tree)
2663
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2665
def test_symlink_content_summary(self):
2666
self.requireFeature(SymlinkFeature)
2667
preview = self.get_empty_preview()
2668
preview.new_symlink('path', preview.root, 'target', 'path-id')
2669
summary = preview.get_preview_tree().path_content_summary('path')
2670
self.assertEqual(('symlink', None, None, 'target'), summary)
2672
def test_missing_content_summary(self):
2673
preview = self.get_empty_preview()
2674
summary = preview.get_preview_tree().path_content_summary('path')
2675
self.assertEqual(('missing', None, None, None), summary)
2677
def test_deleted_content_summary(self):
2678
tree = self.make_branch_and_tree('tree')
2679
self.build_tree(['tree/path/'])
2681
preview = TransformPreview(tree)
2682
self.addCleanup(preview.finalize)
2683
preview.delete_contents(preview.trans_id_tree_path('path'))
2684
summary = preview.get_preview_tree().path_content_summary('path')
2685
self.assertEqual(('missing', None, None, None), summary)
2687
def test_file_content_summary_executable(self):
2688
preview = self.get_empty_preview()
2689
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2690
preview.set_executability(True, path_id)
2691
summary = preview.get_preview_tree().path_content_summary('path')
2692
self.assertEqual(4, len(summary))
2693
self.assertEqual('file', summary[0])
2694
# size must be known
2695
self.assertEqual(len('contents'), summary[1])
2697
self.assertEqual(True, summary[2])
2698
# will not have hash (not cheap to determine)
2699
self.assertIs(None, summary[3])
2701
def test_change_executability(self):
2702
tree = self.make_branch_and_tree('tree')
2703
self.build_tree(['tree/path'])
2705
preview = TransformPreview(tree)
2706
self.addCleanup(preview.finalize)
2707
path_id = preview.trans_id_tree_path('path')
2708
preview.set_executability(True, path_id)
2709
summary = preview.get_preview_tree().path_content_summary('path')
2710
self.assertEqual(True, summary[2])
2712
def test_file_content_summary_non_exec(self):
2713
preview = self.get_empty_preview()
2714
preview.new_file('path', preview.root, 'contents', 'path-id')
2715
summary = preview.get_preview_tree().path_content_summary('path')
2716
self.assertEqual(4, len(summary))
2717
self.assertEqual('file', summary[0])
2718
# size must be known
2719
self.assertEqual(len('contents'), summary[1])
2721
self.assertEqual(False, summary[2])
2722
# will not have hash (not cheap to determine)
2723
self.assertIs(None, summary[3])
2725
def test_dir_content_summary(self):
2726
preview = self.get_empty_preview()
2727
preview.new_directory('path', preview.root, 'path-id')
2728
summary = preview.get_preview_tree().path_content_summary('path')
2729
self.assertEqual(('directory', None, None, None), summary)
2731
def test_tree_content_summary(self):
2732
preview = self.get_empty_preview()
2733
path = preview.new_directory('path', preview.root, 'path-id')
2734
preview.set_tree_reference('rev-1', path)
2735
summary = preview.get_preview_tree().path_content_summary('path')
2736
self.assertEqual(4, len(summary))
2737
self.assertEqual('tree-reference', summary[0])
2739
def test_annotate(self):
2740
tree = self.make_branch_and_tree('tree')
2741
self.build_tree_contents([('tree/file', 'a\n')])
2742
tree.add('file', 'file-id')
2743
tree.commit('a', rev_id='one')
2744
self.build_tree_contents([('tree/file', 'a\nb\n')])
2745
preview = TransformPreview(tree)
2746
self.addCleanup(preview.finalize)
2747
file_trans_id = preview.trans_id_file_id('file-id')
2748
preview.delete_contents(file_trans_id)
2749
preview.create_file('a\nb\nc\n', file_trans_id)
2750
preview_tree = preview.get_preview_tree()
2756
annotation = preview_tree.annotate_iter('file-id', 'me:')
2757
self.assertEqual(expected, annotation)
2759
def test_annotate_missing(self):
2760
preview = self.get_empty_preview()
2761
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2762
preview_tree = preview.get_preview_tree()
2768
annotation = preview_tree.annotate_iter('file-id', 'me:')
2769
self.assertEqual(expected, annotation)
2771
def test_annotate_rename(self):
2772
tree = self.make_branch_and_tree('tree')
2773
self.build_tree_contents([('tree/file', 'a\n')])
2774
tree.add('file', 'file-id')
2775
tree.commit('a', rev_id='one')
2776
preview = TransformPreview(tree)
2777
self.addCleanup(preview.finalize)
2778
file_trans_id = preview.trans_id_file_id('file-id')
2779
preview.adjust_path('newname', preview.root, file_trans_id)
2780
preview_tree = preview.get_preview_tree()
2784
annotation = preview_tree.annotate_iter('file-id', 'me:')
2785
self.assertEqual(expected, annotation)
2787
def test_annotate_deleted(self):
2788
tree = self.make_branch_and_tree('tree')
2789
self.build_tree_contents([('tree/file', 'a\n')])
2790
tree.add('file', 'file-id')
2791
tree.commit('a', rev_id='one')
2792
self.build_tree_contents([('tree/file', 'a\nb\n')])
2793
preview = TransformPreview(tree)
2794
self.addCleanup(preview.finalize)
2795
file_trans_id = preview.trans_id_file_id('file-id')
2796
preview.delete_contents(file_trans_id)
2797
preview_tree = preview.get_preview_tree()
2798
annotation = preview_tree.annotate_iter('file-id', 'me:')
2799
self.assertIs(None, annotation)
2801
def test_stored_kind(self):
2802
preview = self.get_empty_preview()
2803
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2804
preview_tree = preview.get_preview_tree()
2805
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2807
def test_is_executable(self):
2808
preview = self.get_empty_preview()
2809
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2810
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2811
preview_tree = preview.get_preview_tree()
2812
self.assertEqual(True, preview_tree.is_executable('file-id'))
2814
def test_get_set_parent_ids(self):
2815
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2816
self.assertEqual([], preview_tree.get_parent_ids())
2817
preview_tree.set_parent_ids(['rev-1'])
2818
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2820
def test_plan_file_merge(self):
2821
work_a = self.make_branch_and_tree('wta')
2822
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2823
work_a.add('file', 'file-id')
2824
base_id = work_a.commit('base version')
2825
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2826
preview = TransformPreview(work_a)
2827
self.addCleanup(preview.finalize)
2828
trans_id = preview.trans_id_file_id('file-id')
2829
preview.delete_contents(trans_id)
2830
preview.create_file('b\nc\nd\ne\n', trans_id)
2831
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2832
tree_a = preview.get_preview_tree()
2833
tree_a.set_parent_ids([base_id])
2835
('killed-a', 'a\n'),
2836
('killed-b', 'b\n'),
2837
('unchanged', 'c\n'),
2838
('unchanged', 'd\n'),
2841
], list(tree_a.plan_file_merge('file-id', tree_b)))
2843
def test_plan_file_merge_revision_tree(self):
2844
work_a = self.make_branch_and_tree('wta')
2845
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2846
work_a.add('file', 'file-id')
2847
base_id = work_a.commit('base version')
2848
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2849
preview = TransformPreview(work_a.basis_tree())
2850
self.addCleanup(preview.finalize)
2851
trans_id = preview.trans_id_file_id('file-id')
2852
preview.delete_contents(trans_id)
2853
preview.create_file('b\nc\nd\ne\n', trans_id)
2854
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2855
tree_a = preview.get_preview_tree()
2856
tree_a.set_parent_ids([base_id])
2858
('killed-a', 'a\n'),
2859
('killed-b', 'b\n'),
2860
('unchanged', 'c\n'),
2861
('unchanged', 'd\n'),
2864
], list(tree_a.plan_file_merge('file-id', tree_b)))
2866
def test_walkdirs(self):
2867
preview = self.get_empty_preview()
2868
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2869
# FIXME: new_directory should mark root.
2870
preview.fixup_new_roots()
2871
preview_tree = preview.get_preview_tree()
2872
file_trans_id = preview.new_file('a', preview.root, 'contents',
2874
expected = [(('', 'tree-root'),
2875
[('a', 'a', 'file', None, 'a-id', 'file')])]
2876
self.assertEqual(expected, list(preview_tree.walkdirs()))
2878
def test_extras(self):
2879
work_tree = self.make_branch_and_tree('tree')
2880
self.build_tree(['tree/removed-file', 'tree/existing-file',
2881
'tree/not-removed-file'])
2882
work_tree.add(['removed-file', 'not-removed-file'])
2883
preview = TransformPreview(work_tree)
2884
self.addCleanup(preview.finalize)
2885
preview.new_file('new-file', preview.root, 'contents')
2886
preview.new_file('new-versioned-file', preview.root, 'contents',
2888
tree = preview.get_preview_tree()
2889
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2890
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2893
def test_merge_into_preview(self):
2894
work_tree = self.make_branch_and_tree('tree')
2895
self.build_tree_contents([('tree/file','b\n')])
2896
work_tree.add('file', 'file-id')
2897
work_tree.commit('first commit')
2898
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2899
self.build_tree_contents([('child/file','b\nc\n')])
2900
child_tree.commit('child commit')
2901
child_tree.lock_write()
2902
self.addCleanup(child_tree.unlock)
2903
work_tree.lock_write()
2904
self.addCleanup(work_tree.unlock)
2905
preview = TransformPreview(work_tree)
2906
self.addCleanup(preview.finalize)
2907
file_trans_id = preview.trans_id_file_id('file-id')
2908
preview.delete_contents(file_trans_id)
2909
preview.create_file('a\nb\n', file_trans_id)
2910
preview_tree = preview.get_preview_tree()
2911
merger = Merger.from_revision_ids(None, preview_tree,
2912
child_tree.branch.last_revision(),
2913
other_branch=child_tree.branch,
2914
tree_branch=work_tree.branch)
2915
merger.merge_type = Merge3Merger
2916
tt = merger.make_merger().make_preview_transform()
2917
self.addCleanup(tt.finalize)
2918
final_tree = tt.get_preview_tree()
2919
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2921
def test_merge_preview_into_workingtree(self):
2922
tree = self.make_branch_and_tree('tree')
2923
tree.set_root_id('TREE_ROOT')
2924
tt = TransformPreview(tree)
2925
self.addCleanup(tt.finalize)
2926
tt.new_file('name', tt.root, 'content', 'file-id')
2927
tree2 = self.make_branch_and_tree('tree2')
2928
tree2.set_root_id('TREE_ROOT')
2929
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2930
None, tree.basis_tree())
2931
merger.merge_type = Merge3Merger
2934
def test_merge_preview_into_workingtree_handles_conflicts(self):
2935
tree = self.make_branch_and_tree('tree')
2936
self.build_tree_contents([('tree/foo', 'bar')])
2937
tree.add('foo', 'foo-id')
2939
tt = TransformPreview(tree)
2940
self.addCleanup(tt.finalize)
2941
trans_id = tt.trans_id_file_id('foo-id')
2942
tt.delete_contents(trans_id)
2943
tt.create_file('baz', trans_id)
2944
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2945
self.build_tree_contents([('tree2/foo', 'qux')])
2947
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2948
pb, tree.basis_tree())
2949
merger.merge_type = Merge3Merger
2952
def test_is_executable(self):
2953
tree = self.make_branch_and_tree('tree')
2954
preview = TransformPreview(tree)
2955
self.addCleanup(preview.finalize)
2956
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2957
preview_tree = preview.get_preview_tree()
2958
self.assertEqual(False, preview_tree.is_executable('baz-id',
2960
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2962
def test_commit_preview_tree(self):
2963
tree = self.make_branch_and_tree('tree')
2964
rev_id = tree.commit('rev1')
2965
tree.branch.lock_write()
2966
self.addCleanup(tree.branch.unlock)
2967
tt = TransformPreview(tree)
2968
tt.new_file('file', tt.root, 'contents', 'file_id')
2969
self.addCleanup(tt.finalize)
2970
preview = tt.get_preview_tree()
2971
preview.set_parent_ids([rev_id])
2972
builder = tree.branch.get_commit_builder([rev_id])
2973
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2974
builder.finish_inventory()
2975
rev2_id = builder.commit('rev2')
2976
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2977
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2979
def test_ascii_limbo_paths(self):
2980
self.requireFeature(tests.UnicodeFilenameFeature)
2981
branch = self.make_branch('any')
2982
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2983
tt = TransformPreview(tree)
2984
self.addCleanup(tt.finalize)
2985
foo_id = tt.new_directory('', ROOT_PARENT)
2986
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
2987
limbo_path = tt._limbo_name(bar_id)
2988
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
2991
class FakeSerializer(object):
2992
"""Serializer implementation that simply returns the input.
2994
The input is returned in the order used by pack.ContainerPushParser.
2997
def bytes_record(bytes, names):
3001
class TestSerializeTransform(tests.TestCaseWithTransport):
3003
_test_needs_features = [tests.UnicodeFilenameFeature]
3005
def get_preview(self, tree=None):
3007
tree = self.make_branch_and_tree('tree')
3008
tt = TransformPreview(tree)
3009
self.addCleanup(tt.finalize)
3012
def assertSerializesTo(self, expected, tt):
3013
records = list(tt.serialize(FakeSerializer()))
3014
self.assertEqual(expected, records)
3017
def default_attribs():
3022
'_new_executability': {},
3024
'_tree_path_ids': {'': 'new-0'},
3026
'_removed_contents': [],
3027
'_non_present_ids': {},
3030
def make_records(self, attribs, contents):
3032
(((('attribs'),),), bencode.bencode(attribs))]
3033
records.extend([(((n, k),), c) for n, k, c in contents])
3036
def creation_records(self):
3037
attribs = self.default_attribs()
3038
attribs['_id_number'] = 3
3039
attribs['_new_name'] = {
3040
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
3041
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
3042
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
3043
attribs['_new_executability'] = {'new-1': 1}
3045
('new-1', 'file', 'i 1\nbar\n'),
3046
('new-2', 'directory', ''),
3048
return self.make_records(attribs, contents)
3050
def test_serialize_creation(self):
3051
tt = self.get_preview()
3052
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
3053
tt.new_directory('qux', tt.root, 'quxx')
3054
self.assertSerializesTo(self.creation_records(), tt)
3056
def test_deserialize_creation(self):
3057
tt = self.get_preview()
3058
tt.deserialize(iter(self.creation_records()))
3059
self.assertEqual(3, tt._id_number)
3060
self.assertEqual({'new-1': u'foo\u1234',
3061
'new-2': 'qux'}, tt._new_name)
3062
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
3063
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
3064
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
3065
self.assertEqual({'new-1': True}, tt._new_executability)
3066
self.assertEqual({'new-1': 'file',
3067
'new-2': 'directory'}, tt._new_contents)
3068
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
3070
foo_content = foo_limbo.read()
3073
self.assertEqual('bar', foo_content)
3075
def symlink_creation_records(self):
3076
attribs = self.default_attribs()
3077
attribs['_id_number'] = 2
3078
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3079
attribs['_new_parent'] = {'new-1': 'new-0'}
3080
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3081
return self.make_records(attribs, contents)
3083
def test_serialize_symlink_creation(self):
3084
self.requireFeature(tests.SymlinkFeature)
3085
tt = self.get_preview()
3086
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3087
self.assertSerializesTo(self.symlink_creation_records(), tt)
3089
def test_deserialize_symlink_creation(self):
3090
self.requireFeature(tests.SymlinkFeature)
3091
tt = self.get_preview()
3092
tt.deserialize(iter(self.symlink_creation_records()))
3093
abspath = tt._limbo_name('new-1')
3094
foo_content = osutils.readlink(abspath)
3095
self.assertEqual(u'bar\u1234', foo_content)
3097
def make_destruction_preview(self):
3098
tree = self.make_branch_and_tree('.')
3099
self.build_tree([u'foo\u1234', 'bar'])
3100
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3101
return self.get_preview(tree)
3103
def destruction_records(self):
3104
attribs = self.default_attribs()
3105
attribs['_id_number'] = 3
3106
attribs['_removed_id'] = ['new-1']
3107
attribs['_removed_contents'] = ['new-2']
3108
attribs['_tree_path_ids'] = {
3110
u'foo\u1234'.encode('utf-8'): 'new-1',
3113
return self.make_records(attribs, [])
3115
def test_serialize_destruction(self):
3116
tt = self.make_destruction_preview()
3117
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3118
tt.unversion_file(foo_trans_id)
3119
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3120
tt.delete_contents(bar_trans_id)
3121
self.assertSerializesTo(self.destruction_records(), tt)
3123
def test_deserialize_destruction(self):
3124
tt = self.make_destruction_preview()
3125
tt.deserialize(iter(self.destruction_records()))
3126
self.assertEqual({u'foo\u1234': 'new-1',
3128
'': tt.root}, tt._tree_path_ids)
3129
self.assertEqual({'new-1': u'foo\u1234',
3131
tt.root: ''}, tt._tree_id_paths)
3132
self.assertEqual(set(['new-1']), tt._removed_id)
3133
self.assertEqual(set(['new-2']), tt._removed_contents)
3135
def missing_records(self):
3136
attribs = self.default_attribs()
3137
attribs['_id_number'] = 2
3138
attribs['_non_present_ids'] = {
3140
return self.make_records(attribs, [])
3142
def test_serialize_missing(self):
3143
tt = self.get_preview()
3144
boo_trans_id = tt.trans_id_file_id('boo')
3145
self.assertSerializesTo(self.missing_records(), tt)
3147
def test_deserialize_missing(self):
3148
tt = self.get_preview()
3149
tt.deserialize(iter(self.missing_records()))
3150
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3152
def make_modification_preview(self):
3153
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3154
LINES_TWO = 'z\nbb\nx\ndd\n'
3155
tree = self.make_branch_and_tree('tree')
3156
self.build_tree_contents([('tree/file', LINES_ONE)])
3157
tree.add('file', 'file-id')
3158
return self.get_preview(tree), LINES_TWO
3160
def modification_records(self):
3161
attribs = self.default_attribs()
3162
attribs['_id_number'] = 2
3163
attribs['_tree_path_ids'] = {
3166
attribs['_removed_contents'] = ['new-1']
3167
contents = [('new-1', 'file',
3168
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3169
return self.make_records(attribs, contents)
3171
def test_serialize_modification(self):
3172
tt, LINES = self.make_modification_preview()
3173
trans_id = tt.trans_id_file_id('file-id')
3174
tt.delete_contents(trans_id)
3175
tt.create_file(LINES, trans_id)
3176
self.assertSerializesTo(self.modification_records(), tt)
3178
def test_deserialize_modification(self):
3179
tt, LINES = self.make_modification_preview()
3180
tt.deserialize(iter(self.modification_records()))
3181
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3183
def make_kind_change_preview(self):
3184
LINES = 'a\nb\nc\nd\n'
3185
tree = self.make_branch_and_tree('tree')
3186
self.build_tree(['tree/foo/'])
3187
tree.add('foo', 'foo-id')
3188
return self.get_preview(tree), LINES
3190
def kind_change_records(self):
3191
attribs = self.default_attribs()
3192
attribs['_id_number'] = 2
3193
attribs['_tree_path_ids'] = {
3196
attribs['_removed_contents'] = ['new-1']
3197
contents = [('new-1', 'file',
3198
'i 4\na\nb\nc\nd\n\n')]
3199
return self.make_records(attribs, contents)
3201
def test_serialize_kind_change(self):
3202
tt, LINES = self.make_kind_change_preview()
3203
trans_id = tt.trans_id_file_id('foo-id')
3204
tt.delete_contents(trans_id)
3205
tt.create_file(LINES, trans_id)
3206
self.assertSerializesTo(self.kind_change_records(), tt)
3208
def test_deserialize_kind_change(self):
3209
tt, LINES = self.make_kind_change_preview()
3210
tt.deserialize(iter(self.kind_change_records()))
3211
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3213
def make_add_contents_preview(self):
3214
LINES = 'a\nb\nc\nd\n'
3215
tree = self.make_branch_and_tree('tree')
3216
self.build_tree(['tree/foo'])
3218
os.unlink('tree/foo')
3219
return self.get_preview(tree), LINES
3221
def add_contents_records(self):
3222
attribs = self.default_attribs()
3223
attribs['_id_number'] = 2
3224
attribs['_tree_path_ids'] = {
3227
contents = [('new-1', 'file',
3228
'i 4\na\nb\nc\nd\n\n')]
3229
return self.make_records(attribs, contents)
3231
def test_serialize_add_contents(self):
3232
tt, LINES = self.make_add_contents_preview()
3233
trans_id = tt.trans_id_tree_path('foo')
3234
tt.create_file(LINES, trans_id)
3235
self.assertSerializesTo(self.add_contents_records(), tt)
3237
def test_deserialize_add_contents(self):
3238
tt, LINES = self.make_add_contents_preview()
3239
tt.deserialize(iter(self.add_contents_records()))
3240
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3242
def test_get_parents_lines(self):
3243
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3244
LINES_TWO = 'z\nbb\nx\ndd\n'
3245
tree = self.make_branch_and_tree('tree')
3246
self.build_tree_contents([('tree/file', LINES_ONE)])
3247
tree.add('file', 'file-id')
3248
tt = self.get_preview(tree)
3249
trans_id = tt.trans_id_tree_path('file')
3250
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3251
tt._get_parents_lines(trans_id))
3253
def test_get_parents_texts(self):
3254
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3255
LINES_TWO = 'z\nbb\nx\ndd\n'
3256
tree = self.make_branch_and_tree('tree')
3257
self.build_tree_contents([('tree/file', LINES_ONE)])
3258
tree.add('file', 'file-id')
3259
tt = self.get_preview(tree)
3260
trans_id = tt.trans_id_tree_path('file')
3261
self.assertEqual((LINES_ONE,),
3262
tt._get_parents_texts(trans_id))
3265
class TestOrphan(tests.TestCaseWithTransport):
3267
def test_no_orphan_for_transform_preview(self):
3268
tree = self.make_branch_and_tree('tree')
3269
tt = transform.TransformPreview(tree)
3270
self.addCleanup(tt.finalize)
3271
self.assertRaises(NotImplementedError, tt.new_orphan, 'foo', 'bar')
3273
def _set_orphan_policy(self, wt, policy):
3274
wt.branch.get_config().set_user_option('bzr.transform.orphan_policy',
3277
def _prepare_orphan(self, wt):
3278
self.build_tree(['dir/', 'dir/file', 'dir/foo'])
3279
wt.add(['dir', 'dir/file'], ['dir-id', 'file-id'])
3280
wt.commit('add dir and file ignoring foo')
3281
tt = transform.TreeTransform(wt)
3282
self.addCleanup(tt.finalize)
3283
# dir and bar are deleted
3284
dir_tid = tt.trans_id_tree_path('dir')
3285
file_tid = tt.trans_id_tree_path('dir/file')
3286
orphan_tid = tt.trans_id_tree_path('dir/foo')
3287
tt.delete_contents(file_tid)
3288
tt.unversion_file(file_tid)
3289
tt.delete_contents(dir_tid)
3290
tt.unversion_file(dir_tid)
3291
# There should be a conflict because dir still contain foo
3292
raw_conflicts = tt.find_conflicts()
3293
self.assertLength(1, raw_conflicts)
3294
self.assertEqual(('missing parent', 'new-1'), raw_conflicts[0])
3295
return tt, orphan_tid
3297
def test_new_orphan_created(self):
3298
wt = self.make_branch_and_tree('.')
3299
self._set_orphan_policy(wt, 'move')
3300
tt, orphan_tid = self._prepare_orphan(wt)
3303
warnings.append(args[0] % args[1:])
3304
self.overrideAttr(trace, 'warning', warning)
3305
remaining_conflicts = resolve_conflicts(tt)
3306
self.assertEquals(['dir/foo has been orphaned in bzr-orphans'],
3308
# Yeah for resolved conflicts !
3309
self.assertLength(0, remaining_conflicts)
3310
# We have a new orphan
3311
self.assertEquals('foo.~1~', tt.final_name(orphan_tid))
3312
self.assertEquals('bzr-orphans',
3313
tt.final_name(tt.final_parent(orphan_tid)))
3315
def test_never_orphan(self):
3316
wt = self.make_branch_and_tree('.')
3317
self._set_orphan_policy(wt, 'conflict')
3318
tt, orphan_tid = self._prepare_orphan(wt)
3319
remaining_conflicts = resolve_conflicts(tt)
3320
self.assertLength(1, remaining_conflicts)
3321
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3322
remaining_conflicts.pop())
3324
def test_orphan_error(self):
3325
def bogus_orphan(tt, orphan_id, parent_id):
3326
raise transform.OrphaningError(tt.final_name(orphan_id),
3327
tt.final_name(parent_id))
3328
transform.orphaning_registry.register('bogus', bogus_orphan,
3329
'Raise an error when orphaning')
3330
wt = self.make_branch_and_tree('.')
3331
self._set_orphan_policy(wt, 'bogus')
3332
tt, orphan_tid = self._prepare_orphan(wt)
3333
remaining_conflicts = resolve_conflicts(tt)
3334
self.assertLength(1, remaining_conflicts)
3335
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3336
remaining_conflicts.pop())
3338
def test_unknown_orphan_policy(self):
3339
wt = self.make_branch_and_tree('.')
3340
# Set a fictional policy nobody ever implemented
3341
self._set_orphan_policy(wt, 'donttouchmypreciouuus')
3342
tt, orphan_tid = self._prepare_orphan(wt)
3345
warnings.append(args[0] % args[1:])
3346
self.overrideAttr(trace, 'warning', warning)
3347
remaining_conflicts = resolve_conflicts(tt)
3348
# We fallback to the default policy which create a conflict
3349
self.assertLength(1, remaining_conflicts)
3350
self.assertEqual(('deleting parent', 'Not deleting', 'new-1'),
3351
remaining_conflicts.pop())
3352
self.assertLength(1, warnings)
3353
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~')