1090
764
old = transform.trans_id_tree_path('old')
1091
765
subdir = transform.trans_id_tree_file_id('subdir-id')
1092
self.assertEqual([], list(transform.iter_changes()))
766
self.assertEqual([], list(transform._iter_changes()))
1093
767
transform.delete_contents(subdir)
1094
768
transform.create_directory(subdir)
1095
769
transform.set_executability(False, old)
1096
770
transform.unversion_file(old)
1097
771
transform.version_file('id-1', old)
1098
772
transform.adjust_path('old', root, old)
1099
self.assertEqual([], list(transform.iter_changes()))
1101
transform.finalize()
1103
def test_rename_count(self):
1104
transform, root = self.get_transform()
1105
transform.new_file('name1', root, 'contents')
1106
self.assertEqual(transform.rename_count, 0)
1108
self.assertEqual(transform.rename_count, 1)
1109
transform2, root = self.get_transform()
1110
transform2.adjust_path('name2', root,
1111
transform2.trans_id_tree_path('name1'))
1112
self.assertEqual(transform2.rename_count, 0)
1114
self.assertEqual(transform2.rename_count, 2)
1116
def test_change_parent(self):
1117
"""Ensure that after we change a parent, the results are still right.
1119
Renames and parent changes on pending transforms can happen as part
1120
of conflict resolution, and are explicitly permitted by the
1123
This test ensures they work correctly with the rename-avoidance
1126
transform, root = self.get_transform()
1127
parent1 = transform.new_directory('parent1', root)
1128
child1 = transform.new_file('child1', parent1, 'contents')
1129
parent2 = transform.new_directory('parent2', root)
1130
transform.adjust_path('child1', parent2, child1)
1132
self.failIfExists(self.wt.abspath('parent1/child1'))
1133
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1134
# rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1135
# no rename for child1 (counting only renames during apply)
1136
self.failUnlessEqual(2, transform.rename_count)
1138
def test_cancel_parent(self):
1139
"""Cancelling a parent doesn't cause deletion of a non-empty directory
1141
This is like the test_change_parent, except that we cancel the parent
1142
before adjusting the path. The transform must detect that the
1143
directory is non-empty, and move children to safe locations.
1145
transform, root = self.get_transform()
1146
parent1 = transform.new_directory('parent1', root)
1147
child1 = transform.new_file('child1', parent1, 'contents')
1148
child2 = transform.new_file('child2', parent1, 'contents')
1150
transform.cancel_creation(parent1)
1152
self.fail('Failed to move child1 before deleting parent1')
1153
transform.cancel_creation(child2)
1154
transform.create_directory(parent1)
1156
transform.cancel_creation(parent1)
1157
# If the transform incorrectly believes that child2 is still in
1158
# parent1's limbo directory, it will try to rename it and fail
1159
# because was already moved by the first cancel_creation.
1161
self.fail('Transform still thinks child2 is a child of parent1')
1162
parent2 = transform.new_directory('parent2', root)
1163
transform.adjust_path('child1', parent2, child1)
1165
self.failIfExists(self.wt.abspath('parent1'))
1166
self.failUnlessExists(self.wt.abspath('parent2/child1'))
1167
# rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1168
self.failUnlessEqual(2, transform.rename_count)
1170
def test_adjust_and_cancel(self):
1171
"""Make sure adjust_path keeps track of limbo children properly"""
1172
transform, root = self.get_transform()
1173
parent1 = transform.new_directory('parent1', root)
1174
child1 = transform.new_file('child1', parent1, 'contents')
1175
parent2 = transform.new_directory('parent2', root)
1176
transform.adjust_path('child1', parent2, child1)
1177
transform.cancel_creation(child1)
1179
transform.cancel_creation(parent1)
1180
# if the transform thinks child1 is still in parent1's limbo
1181
# directory, it will attempt to move it and fail.
1183
self.fail('Transform still thinks child1 is a child of parent1')
1184
transform.finalize()
1186
def test_noname_contents(self):
1187
"""TreeTransform should permit deferring naming files."""
1188
transform, root = self.get_transform()
1189
parent = transform.trans_id_file_id('parent-id')
1191
transform.create_directory(parent)
1193
self.fail("Can't handle contents with no name")
1194
transform.finalize()
1196
def test_noname_contents_nested(self):
1197
"""TreeTransform should permit deferring naming files."""
1198
transform, root = self.get_transform()
1199
parent = transform.trans_id_file_id('parent-id')
1201
transform.create_directory(parent)
1203
self.fail("Can't handle contents with no name")
1204
child = transform.new_directory('child', parent)
1205
transform.adjust_path('parent', root, parent)
1207
self.failUnlessExists(self.wt.abspath('parent/child'))
1208
self.assertEqual(1, transform.rename_count)
1210
def test_reuse_name(self):
1211
"""Avoid reusing the same limbo name for different files"""
1212
transform, root = self.get_transform()
1213
parent = transform.new_directory('parent', root)
1214
child1 = transform.new_directory('child', parent)
1216
child2 = transform.new_directory('child', parent)
1218
self.fail('Tranform tried to use the same limbo name twice')
1219
transform.adjust_path('child2', parent, child2)
1221
# limbo/new-1 => parent, limbo/new-3 => parent/child2
1222
# child2 is put into top-level limbo because child1 has already
1223
# claimed the direct limbo path when child2 is created. There is no
1224
# advantage in renaming files once they're in top-level limbo, except
1226
self.assertEqual(2, transform.rename_count)
1228
def test_reuse_when_first_moved(self):
1229
"""Don't avoid direct paths when it is safe to use them"""
1230
transform, root = self.get_transform()
1231
parent = transform.new_directory('parent', root)
1232
child1 = transform.new_directory('child', parent)
1233
transform.adjust_path('child1', parent, child1)
1234
child2 = transform.new_directory('child', parent)
1236
# limbo/new-1 => parent
1237
self.assertEqual(1, transform.rename_count)
1239
def test_reuse_after_cancel(self):
1240
"""Don't avoid direct paths when it is safe to use them"""
1241
transform, root = self.get_transform()
1242
parent2 = transform.new_directory('parent2', root)
1243
child1 = transform.new_directory('child1', parent2)
1244
transform.cancel_creation(parent2)
1245
transform.create_directory(parent2)
1246
child2 = transform.new_directory('child1', parent2)
1247
transform.adjust_path('child2', parent2, child1)
1249
# limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1250
self.assertEqual(2, transform.rename_count)
1252
def test_finalize_order(self):
1253
"""Finalize must be done in child-to-parent order"""
1254
transform, root = self.get_transform()
1255
parent = transform.new_directory('parent', root)
1256
child = transform.new_directory('child', parent)
1258
transform.finalize()
1260
self.fail('Tried to remove parent before child1')
1262
def test_cancel_with_cancelled_child_should_succeed(self):
1263
transform, root = self.get_transform()
1264
parent = transform.new_directory('parent', root)
1265
child = transform.new_directory('child', parent)
1266
transform.cancel_creation(child)
1267
transform.cancel_creation(parent)
1268
transform.finalize()
1270
def test_rollback_on_directory_clash(self):
1272
wt = self.make_branch_and_tree('.')
1273
tt = TreeTransform(wt) # TreeTransform obtains write lock
1275
foo = tt.new_directory('foo', tt.root)
1276
tt.new_file('bar', foo, 'foobar')
1277
baz = tt.new_directory('baz', tt.root)
1278
tt.new_file('qux', baz, 'quux')
1279
# Ask for a rename 'foo' -> 'baz'
1280
tt.adjust_path('baz', tt.root, foo)
1281
# Lie to tt that we've already resolved all conflicts.
1282
tt.apply(no_conflicts=True)
1286
# The rename will fail because the target directory is not empty (but
1287
# raises FileExists anyway).
1288
err = self.assertRaises(errors.FileExists, tt_helper)
1289
self.assertContainsRe(str(err),
1290
"^File exists: .+/baz")
1292
def test_two_directories_clash(self):
1294
wt = self.make_branch_and_tree('.')
1295
tt = TreeTransform(wt) # TreeTransform obtains write lock
1297
foo_1 = tt.new_directory('foo', tt.root)
1298
tt.new_directory('bar', foo_1)
1299
# Adding the same directory with a different content
1300
foo_2 = tt.new_directory('foo', tt.root)
1301
tt.new_directory('baz', foo_2)
1302
# Lie to tt that we've already resolved all conflicts.
1303
tt.apply(no_conflicts=True)
1307
err = self.assertRaises(errors.FileExists, tt_helper)
1308
self.assertContainsRe(str(err),
1309
"^File exists: .+/foo")
1311
def test_two_directories_clash_finalize(self):
1313
wt = self.make_branch_and_tree('.')
1314
tt = TreeTransform(wt) # TreeTransform obtains write lock
1316
foo_1 = tt.new_directory('foo', tt.root)
1317
tt.new_directory('bar', foo_1)
1318
# Adding the same directory with a different content
1319
foo_2 = tt.new_directory('foo', tt.root)
1320
tt.new_directory('baz', foo_2)
1321
# Lie to tt that we've already resolved all conflicts.
1322
tt.apply(no_conflicts=True)
1326
err = self.assertRaises(errors.FileExists, tt_helper)
1327
self.assertContainsRe(str(err),
1328
"^File exists: .+/foo")
1330
def test_file_to_directory(self):
1331
wt = self.make_branch_and_tree('.')
1332
self.build_tree(['foo'])
1335
tt = TreeTransform(wt)
1336
self.addCleanup(tt.finalize)
1337
foo_trans_id = tt.trans_id_tree_path("foo")
1338
tt.delete_contents(foo_trans_id)
1339
tt.create_directory(foo_trans_id)
1340
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1341
tt.create_file(["aa\n"], bar_trans_id)
1342
tt.version_file("bar-1", bar_trans_id)
1344
self.failUnlessExists("foo/bar")
1347
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1352
changes = wt.changes_from(wt.basis_tree())
1353
self.assertFalse(changes.has_changed(), changes)
1355
def test_file_to_symlink(self):
1356
self.requireFeature(SymlinkFeature)
1357
wt = self.make_branch_and_tree('.')
1358
self.build_tree(['foo'])
1361
tt = TreeTransform(wt)
1362
self.addCleanup(tt.finalize)
1363
foo_trans_id = tt.trans_id_tree_path("foo")
1364
tt.delete_contents(foo_trans_id)
1365
tt.create_symlink("bar", foo_trans_id)
1367
self.failUnlessExists("foo")
1369
self.addCleanup(wt.unlock)
1370
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1373
def test_dir_to_file(self):
1374
wt = self.make_branch_and_tree('.')
1375
self.build_tree(['foo/', 'foo/bar'])
1376
wt.add(['foo', 'foo/bar'])
1378
tt = TreeTransform(wt)
1379
self.addCleanup(tt.finalize)
1380
foo_trans_id = tt.trans_id_tree_path("foo")
1381
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1382
tt.delete_contents(foo_trans_id)
1383
tt.delete_versioned(bar_trans_id)
1384
tt.create_file(["aa\n"], foo_trans_id)
1386
self.failUnlessExists("foo")
1388
self.addCleanup(wt.unlock)
1389
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1392
def test_dir_to_hardlink(self):
1393
self.requireFeature(HardlinkFeature)
1394
wt = self.make_branch_and_tree('.')
1395
self.build_tree(['foo/', 'foo/bar'])
1396
wt.add(['foo', 'foo/bar'])
1398
tt = TreeTransform(wt)
1399
self.addCleanup(tt.finalize)
1400
foo_trans_id = tt.trans_id_tree_path("foo")
1401
bar_trans_id = tt.trans_id_tree_path("foo/bar")
1402
tt.delete_contents(foo_trans_id)
1403
tt.delete_versioned(bar_trans_id)
1404
self.build_tree(['baz'])
1405
tt.create_hardlink("baz", foo_trans_id)
1407
self.failUnlessExists("foo")
1408
self.failUnlessExists("baz")
1410
self.addCleanup(wt.unlock)
1411
self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
1414
def test_no_final_path(self):
1415
transform, root = self.get_transform()
1416
trans_id = transform.trans_id_file_id('foo')
1417
transform.create_file('bar', trans_id)
1418
transform.cancel_creation(trans_id)
1421
def test_create_from_tree(self):
1422
tree1 = self.make_branch_and_tree('tree1')
1423
self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
1424
tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
1425
tree2 = self.make_branch_and_tree('tree2')
1426
tt = TreeTransform(tree2)
1427
foo_trans_id = tt.create_path('foo', tt.root)
1428
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1429
bar_trans_id = tt.create_path('bar', tt.root)
1430
create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
1432
self.assertEqual('directory', osutils.file_kind('tree2/foo'))
1433
self.assertFileEqual('baz', 'tree2/bar')
1435
def test_create_from_tree_bytes(self):
1436
"""Provided lines are used instead of tree content."""
1437
tree1 = self.make_branch_and_tree('tree1')
1438
self.build_tree_contents([('tree1/foo', 'bar'),])
1439
tree1.add('foo', 'foo-id')
1440
tree2 = self.make_branch_and_tree('tree2')
1441
tt = TreeTransform(tree2)
1442
foo_trans_id = tt.create_path('foo', tt.root)
1443
create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
1445
self.assertFileEqual('qux', 'tree2/foo')
1447
def test_create_from_tree_symlink(self):
1448
self.requireFeature(SymlinkFeature)
1449
tree1 = self.make_branch_and_tree('tree1')
1450
os.symlink('bar', 'tree1/foo')
1451
tree1.add('foo', 'foo-id')
1452
tt = TreeTransform(self.make_branch_and_tree('tree2'))
1453
foo_trans_id = tt.create_path('foo', tt.root)
1454
create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
1456
self.assertEqual('bar', os.readlink('tree2/foo'))
773
self.assertEqual([], list(transform._iter_changes()))
1459
777
class TransformGroup(object):
1461
778
def __init__(self, dirname, root_id):
1462
779
self.name = dirname
1463
780
os.mkdir(dirname)
1794
1112
target = self.make_branch_and_tree('target')
1795
1113
self.build_tree(['target/name'])
1796
1114
target.add('name')
1797
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1115
self.assertRaises(errors.WorkingTreeAlreadyPopulated,
1798
1116
build_tree, source.basis_tree(), target)
1800
def test_build_tree_rename_count(self):
1801
source = self.make_branch_and_tree('source')
1802
self.build_tree(['source/file1', 'source/dir1/'])
1803
source.add(['file1', 'dir1'])
1804
source.commit('add1')
1805
target1 = self.make_branch_and_tree('target1')
1806
transform_result = build_tree(source.basis_tree(), target1)
1807
self.assertEqual(2, transform_result.rename_count)
1809
self.build_tree(['source/dir1/file2'])
1810
source.add(['dir1/file2'])
1811
source.commit('add3')
1812
target2 = self.make_branch_and_tree('target2')
1813
transform_result = build_tree(source.basis_tree(), target2)
1814
# children of non-root directories should not be renamed
1815
self.assertEqual(2, transform_result.rename_count)
1817
def create_ab_tree(self):
1818
"""Create a committed test tree with two files"""
1819
source = self.make_branch_and_tree('source')
1820
self.build_tree_contents([('source/file1', 'A')])
1821
self.build_tree_contents([('source/file2', 'B')])
1822
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1823
source.commit('commit files')
1825
self.addCleanup(source.unlock)
1828
def test_build_tree_accelerator_tree(self):
1829
source = self.create_ab_tree()
1830
self.build_tree_contents([('source/file2', 'C')])
1832
real_source_get_file = source.get_file
1833
def get_file(file_id, path=None):
1834
calls.append(file_id)
1835
return real_source_get_file(file_id, path)
1836
source.get_file = get_file
1837
target = self.make_branch_and_tree('target')
1838
revision_tree = source.basis_tree()
1839
revision_tree.lock_read()
1840
self.addCleanup(revision_tree.unlock)
1841
build_tree(revision_tree, target, source)
1842
self.assertEqual(['file1-id'], calls)
1844
self.addCleanup(target.unlock)
1845
self.assertEqual([], list(target.iter_changes(revision_tree)))
1847
def test_build_tree_accelerator_tree_missing_file(self):
1848
source = self.create_ab_tree()
1849
os.unlink('source/file1')
1850
source.remove(['file2'])
1851
target = self.make_branch_and_tree('target')
1852
revision_tree = source.basis_tree()
1853
revision_tree.lock_read()
1854
self.addCleanup(revision_tree.unlock)
1855
build_tree(revision_tree, target, source)
1857
self.addCleanup(target.unlock)
1858
self.assertEqual([], list(target.iter_changes(revision_tree)))
1860
def test_build_tree_accelerator_wrong_kind(self):
1861
self.requireFeature(SymlinkFeature)
1862
source = self.make_branch_and_tree('source')
1863
self.build_tree_contents([('source/file1', '')])
1864
self.build_tree_contents([('source/file2', '')])
1865
source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1866
source.commit('commit files')
1867
os.unlink('source/file2')
1868
self.build_tree_contents([('source/file2/', 'C')])
1869
os.unlink('source/file1')
1870
os.symlink('file2', 'source/file1')
1872
real_source_get_file = source.get_file
1873
def get_file(file_id, path=None):
1874
calls.append(file_id)
1875
return real_source_get_file(file_id, path)
1876
source.get_file = get_file
1877
target = self.make_branch_and_tree('target')
1878
revision_tree = source.basis_tree()
1879
revision_tree.lock_read()
1880
self.addCleanup(revision_tree.unlock)
1881
build_tree(revision_tree, target, source)
1882
self.assertEqual([], calls)
1884
self.addCleanup(target.unlock)
1885
self.assertEqual([], list(target.iter_changes(revision_tree)))
1887
def test_build_tree_hardlink(self):
1888
self.requireFeature(HardlinkFeature)
1889
source = self.create_ab_tree()
1890
target = self.make_branch_and_tree('target')
1891
revision_tree = source.basis_tree()
1892
revision_tree.lock_read()
1893
self.addCleanup(revision_tree.unlock)
1894
build_tree(revision_tree, target, source, hardlink=True)
1896
self.addCleanup(target.unlock)
1897
self.assertEqual([], list(target.iter_changes(revision_tree)))
1898
source_stat = os.stat('source/file1')
1899
target_stat = os.stat('target/file1')
1900
self.assertEqual(source_stat, target_stat)
1902
# Explicitly disallowing hardlinks should prevent them.
1903
target2 = self.make_branch_and_tree('target2')
1904
build_tree(revision_tree, target2, source, hardlink=False)
1906
self.addCleanup(target2.unlock)
1907
self.assertEqual([], list(target2.iter_changes(revision_tree)))
1908
source_stat = os.stat('source/file1')
1909
target2_stat = os.stat('target2/file1')
1910
self.assertNotEqual(source_stat, target2_stat)
1912
def test_build_tree_accelerator_tree_moved(self):
1913
source = self.make_branch_and_tree('source')
1914
self.build_tree_contents([('source/file1', 'A')])
1915
source.add(['file1'], ['file1-id'])
1916
source.commit('commit files')
1917
source.rename_one('file1', 'file2')
1919
self.addCleanup(source.unlock)
1920
target = self.make_branch_and_tree('target')
1921
revision_tree = source.basis_tree()
1922
revision_tree.lock_read()
1923
self.addCleanup(revision_tree.unlock)
1924
build_tree(revision_tree, target, source)
1926
self.addCleanup(target.unlock)
1927
self.assertEqual([], list(target.iter_changes(revision_tree)))
1929
def test_build_tree_hardlinks_preserve_execute(self):
1930
self.requireFeature(HardlinkFeature)
1931
source = self.create_ab_tree()
1932
tt = TreeTransform(source)
1933
trans_id = tt.trans_id_tree_file_id('file1-id')
1934
tt.set_executability(True, trans_id)
1936
self.assertTrue(source.is_executable('file1-id'))
1937
target = self.make_branch_and_tree('target')
1938
revision_tree = source.basis_tree()
1939
revision_tree.lock_read()
1940
self.addCleanup(revision_tree.unlock)
1941
build_tree(revision_tree, target, source, hardlink=True)
1943
self.addCleanup(target.unlock)
1944
self.assertEqual([], list(target.iter_changes(revision_tree)))
1945
self.assertTrue(source.is_executable('file1-id'))
1947
def install_rot13_content_filter(self, pattern):
1949
# self.addCleanup(filters._reset_registry, filters._reset_registry())
1950
# below, but that looks a bit... hard to read even if it's exactly
1952
original_registry = filters._reset_registry()
1953
def restore_registry():
1954
filters._reset_registry(original_registry)
1955
self.addCleanup(restore_registry)
1956
def rot13(chunks, context=None):
1957
return [''.join(chunks).encode('rot13')]
1958
rot13filter = filters.ContentFilter(rot13, rot13)
1959
filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
1960
os.mkdir(self.test_home_dir + '/.bazaar')
1961
rules_filename = self.test_home_dir + '/.bazaar/rules'
1962
f = open(rules_filename, 'wb')
1963
f.write('[name %s]\nrot13=yes\n' % (pattern,))
1965
def uninstall_rules():
1966
os.remove(rules_filename)
1968
self.addCleanup(uninstall_rules)
1971
def test_build_tree_content_filtered_files_are_not_hardlinked(self):
1972
"""build_tree will not hardlink files that have content filtering rules
1973
applied to them (but will still hardlink other files from the same tree
1976
self.requireFeature(HardlinkFeature)
1977
self.install_rot13_content_filter('file1')
1978
source = self.create_ab_tree()
1979
target = self.make_branch_and_tree('target')
1980
revision_tree = source.basis_tree()
1981
revision_tree.lock_read()
1982
self.addCleanup(revision_tree.unlock)
1983
build_tree(revision_tree, target, source, hardlink=True)
1985
self.addCleanup(target.unlock)
1986
self.assertEqual([], list(target.iter_changes(revision_tree)))
1987
source_stat = os.stat('source/file1')
1988
target_stat = os.stat('target/file1')
1989
self.assertNotEqual(source_stat, target_stat)
1990
source_stat = os.stat('source/file2')
1991
target_stat = os.stat('target/file2')
1992
self.assertEqualStat(source_stat, target_stat)
1994
def test_case_insensitive_build_tree_inventory(self):
1995
if (tests.CaseInsensitiveFilesystemFeature.available()
1996
or tests.CaseInsCasePresFilenameFeature.available()):
1997
raise tests.UnavailableFeature('Fully case sensitive filesystem')
1998
source = self.make_branch_and_tree('source')
1999
self.build_tree(['source/file', 'source/FILE'])
2000
source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
2001
source.commit('added files')
2002
# Don't try this at home, kids!
2003
# Force the tree to report that it is case insensitive
2004
target = self.make_branch_and_tree('target')
2005
target.case_sensitive = False
2006
build_tree(source.basis_tree(), target, source, delta_from_tree=True)
2007
self.assertEqual('file.moved', target.id2path('lower-id'))
2008
self.assertEqual('FILE', target.id2path('upper-id'))
2011
class TestCommitTransform(tests.TestCaseWithTransport):
2013
def get_branch(self):
2014
tree = self.make_branch_and_tree('tree')
2016
self.addCleanup(tree.unlock)
2017
tree.commit('empty commit')
2020
def get_branch_and_transform(self):
2021
branch = self.get_branch()
2022
tt = TransformPreview(branch.basis_tree())
2023
self.addCleanup(tt.finalize)
2026
def test_commit_wrong_basis(self):
2027
branch = self.get_branch()
2028
basis = branch.repository.revision_tree(
2029
_mod_revision.NULL_REVISION)
2030
tt = TransformPreview(basis)
2031
self.addCleanup(tt.finalize)
2032
e = self.assertRaises(ValueError, tt.commit, branch, '')
2033
self.assertEqual('TreeTransform not based on branch basis: null:',
2036
def test_empy_commit(self):
2037
branch, tt = self.get_branch_and_transform()
2038
rev = tt.commit(branch, 'my message')
2039
self.assertEqual(2, branch.revno())
2040
repo = branch.repository
2041
self.assertEqual('my message', repo.get_revision(rev).message)
2043
def test_merge_parents(self):
2044
branch, tt = self.get_branch_and_transform()
2045
rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
2046
self.assertEqual(['rev1b', 'rev1c'],
2047
branch.basis_tree().get_parent_ids()[1:])
2049
def test_first_commit(self):
2050
branch = self.make_branch('branch')
2052
self.addCleanup(branch.unlock)
2053
tt = TransformPreview(branch.basis_tree())
2054
self.addCleanup(tt.finalize)
2055
tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
2056
rev = tt.commit(branch, 'my message')
2057
self.assertEqual([], branch.basis_tree().get_parent_ids())
2058
self.assertNotEqual(_mod_revision.NULL_REVISION,
2059
branch.last_revision())
2061
def test_first_commit_with_merge_parents(self):
2062
branch = self.make_branch('branch')
2064
self.addCleanup(branch.unlock)
2065
tt = TransformPreview(branch.basis_tree())
2066
self.addCleanup(tt.finalize)
2067
e = self.assertRaises(ValueError, tt.commit, branch,
2068
'my message', ['rev1b-id'])
2069
self.assertEqual('Cannot supply merge parents for first commit.',
2071
self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
2073
def test_add_files(self):
2074
branch, tt = self.get_branch_and_transform()
2075
tt.new_file('file', tt.root, 'contents', 'file-id')
2076
trans_id = tt.new_directory('dir', tt.root, 'dir-id')
2077
if SymlinkFeature.available():
2078
tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
2079
rev = tt.commit(branch, 'message')
2080
tree = branch.basis_tree()
2081
self.assertEqual('file', tree.id2path('file-id'))
2082
self.assertEqual('contents', tree.get_file_text('file-id'))
2083
self.assertEqual('dir', tree.id2path('dir-id'))
2084
if SymlinkFeature.available():
2085
self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
2086
self.assertEqual('target', tree.get_symlink_target('symlink-id'))
2088
def test_add_unversioned(self):
2089
branch, tt = self.get_branch_and_transform()
2090
tt.new_file('file', tt.root, 'contents')
2091
self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
2092
'message', strict=True)
2094
def test_modify_strict(self):
2095
branch, tt = self.get_branch_and_transform()
2096
tt.new_file('file', tt.root, 'contents', 'file-id')
2097
tt.commit(branch, 'message', strict=True)
2098
tt = TransformPreview(branch.basis_tree())
2099
self.addCleanup(tt.finalize)
2100
trans_id = tt.trans_id_file_id('file-id')
2101
tt.delete_contents(trans_id)
2102
tt.create_file('contents', trans_id)
2103
tt.commit(branch, 'message', strict=True)
2105
def test_commit_malformed(self):
2106
"""Committing a malformed transform should raise an exception.
2108
In this case, we are adding a file without adding its parent.
2110
branch, tt = self.get_branch_and_transform()
2111
parent_id = tt.trans_id_file_id('parent-id')
2112
tt.new_file('file', parent_id, 'contents', 'file-id')
2113
self.assertRaises(errors.MalformedTransform, tt.commit, branch,
2116
def test_commit_rich_revision_data(self):
2117
branch, tt = self.get_branch_and_transform()
2118
rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
2119
committer='me <me@example.com>',
2120
revprops={'foo': 'bar'}, revision_id='revid-1',
2121
authors=['Author1 <author1@example.com>',
2122
'Author2 <author2@example.com>',
2124
self.assertEqual('revid-1', rev_id)
2125
revision = branch.repository.get_revision(rev_id)
2126
self.assertEqual(1, revision.timestamp)
2127
self.assertEqual(43201, revision.timezone)
2128
self.assertEqual('me <me@example.com>', revision.committer)
2129
self.assertEqual(['Author1 <author1@example.com>',
2130
'Author2 <author2@example.com>'],
2131
revision.get_apparent_authors())
2132
del revision.properties['authors']
2133
self.assertEqual({'foo': 'bar',
2134
'branch-nick': 'tree'},
2135
revision.properties)
2137
def test_no_explicit_revprops(self):
2138
branch, tt = self.get_branch_and_transform()
2139
rev_id = tt.commit(branch, 'message', authors=[
2140
'Author1 <author1@example.com>',
2141
'Author2 <author2@example.com>', ])
2142
revision = branch.repository.get_revision(rev_id)
2143
self.assertEqual(['Author1 <author1@example.com>',
2144
'Author2 <author2@example.com>'],
2145
revision.get_apparent_authors())
2146
self.assertEqual('tree', revision.properties['branch-nick'])
2149
1119
class MockTransform(object):
2177
1145
self.assertEqual(name, 'name.~1~')
2178
1146
name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
2179
1147
self.assertEqual(name, 'name.~4~')
2182
class TestFileMover(tests.TestCaseWithTransport):
2184
def test_file_mover(self):
2185
self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
2186
mover = _FileMover()
2187
mover.rename('a', 'q')
2188
self.failUnlessExists('q')
2189
self.failIfExists('a')
2190
self.failUnlessExists('q/b')
2191
self.failUnlessExists('c')
2192
self.failUnlessExists('c/d')
2194
def test_pre_delete_rollback(self):
2195
self.build_tree(['a/'])
2196
mover = _FileMover()
2197
mover.pre_delete('a', 'q')
2198
self.failUnlessExists('q')
2199
self.failIfExists('a')
2201
self.failIfExists('q')
2202
self.failUnlessExists('a')
2204
def test_apply_deletions(self):
2205
self.build_tree(['a/', 'b/'])
2206
mover = _FileMover()
2207
mover.pre_delete('a', 'q')
2208
mover.pre_delete('b', 'r')
2209
self.failUnlessExists('q')
2210
self.failUnlessExists('r')
2211
self.failIfExists('a')
2212
self.failIfExists('b')
2213
mover.apply_deletions()
2214
self.failIfExists('q')
2215
self.failIfExists('r')
2216
self.failIfExists('a')
2217
self.failIfExists('b')
2219
def test_file_mover_rollback(self):
2220
self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
2221
mover = _FileMover()
2222
mover.rename('c/d', 'c/f')
2223
mover.rename('c/e', 'c/d')
2225
mover.rename('a', 'c')
2226
except errors.FileExists, e:
2228
self.failUnlessExists('a')
2229
self.failUnlessExists('c/d')
2232
class Bogus(Exception):
2236
class TestTransformRollback(tests.TestCaseWithTransport):
2238
class ExceptionFileMover(_FileMover):
2240
def __init__(self, bad_source=None, bad_target=None):
2241
_FileMover.__init__(self)
2242
self.bad_source = bad_source
2243
self.bad_target = bad_target
2245
def rename(self, source, target):
2246
if (self.bad_source is not None and
2247
source.endswith(self.bad_source)):
2249
elif (self.bad_target is not None and
2250
target.endswith(self.bad_target)):
2253
_FileMover.rename(self, source, target)
2255
def test_rollback_rename(self):
2256
tree = self.make_branch_and_tree('.')
2257
self.build_tree(['a/', 'a/b'])
2258
tt = TreeTransform(tree)
2259
self.addCleanup(tt.finalize)
2260
a_id = tt.trans_id_tree_path('a')
2261
tt.adjust_path('c', tt.root, a_id)
2262
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2263
self.assertRaises(Bogus, tt.apply,
2264
_mover=self.ExceptionFileMover(bad_source='a'))
2265
self.failUnlessExists('a')
2266
self.failUnlessExists('a/b')
2268
self.failUnlessExists('c')
2269
self.failUnlessExists('c/d')
2271
def test_rollback_rename_into_place(self):
2272
tree = self.make_branch_and_tree('.')
2273
self.build_tree(['a/', 'a/b'])
2274
tt = TreeTransform(tree)
2275
self.addCleanup(tt.finalize)
2276
a_id = tt.trans_id_tree_path('a')
2277
tt.adjust_path('c', tt.root, a_id)
2278
tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
2279
self.assertRaises(Bogus, tt.apply,
2280
_mover=self.ExceptionFileMover(bad_target='c/d'))
2281
self.failUnlessExists('a')
2282
self.failUnlessExists('a/b')
2284
self.failUnlessExists('c')
2285
self.failUnlessExists('c/d')
2287
def test_rollback_deletion(self):
2288
tree = self.make_branch_and_tree('.')
2289
self.build_tree(['a/', 'a/b'])
2290
tt = TreeTransform(tree)
2291
self.addCleanup(tt.finalize)
2292
a_id = tt.trans_id_tree_path('a')
2293
tt.delete_contents(a_id)
2294
tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
2295
self.assertRaises(Bogus, tt.apply,
2296
_mover=self.ExceptionFileMover(bad_target='d'))
2297
self.failUnlessExists('a')
2298
self.failUnlessExists('a/b')
2300
def test_resolve_no_parent(self):
2301
wt = self.make_branch_and_tree('.')
2302
tt = TreeTransform(wt)
2303
self.addCleanup(tt.finalize)
2304
parent = tt.trans_id_file_id('parent-id')
2305
tt.new_file('file', parent, 'Contents')
2306
resolve_conflicts(tt)
2309
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
2310
('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
2312
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
2313
('', ''), ('directory', 'directory'), (False, None))
2316
class TestTransformPreview(tests.TestCaseWithTransport):
2318
def create_tree(self):
2319
tree = self.make_branch_and_tree('.')
2320
self.build_tree_contents([('a', 'content 1')])
2321
tree.set_root_id('TREE_ROOT')
2322
tree.add('a', 'a-id')
2323
tree.commit('rev1', rev_id='rev1')
2324
return tree.branch.repository.revision_tree('rev1')
2326
def get_empty_preview(self):
2327
repository = self.make_repository('repo')
2328
tree = repository.revision_tree(_mod_revision.NULL_REVISION)
2329
preview = TransformPreview(tree)
2330
self.addCleanup(preview.finalize)
2333
def test_transform_preview(self):
2334
revision_tree = self.create_tree()
2335
preview = TransformPreview(revision_tree)
2336
self.addCleanup(preview.finalize)
2338
def test_transform_preview_tree(self):
2339
revision_tree = self.create_tree()
2340
preview = TransformPreview(revision_tree)
2341
self.addCleanup(preview.finalize)
2342
preview.get_preview_tree()
2344
def test_transform_new_file(self):
2345
revision_tree = self.create_tree()
2346
preview = TransformPreview(revision_tree)
2347
self.addCleanup(preview.finalize)
2348
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2349
preview_tree = preview.get_preview_tree()
2350
self.assertEqual(preview_tree.kind('file2-id'), 'file')
2352
preview_tree.get_file('file2-id').read(), 'content B\n')
2354
def test_diff_preview_tree(self):
2355
revision_tree = self.create_tree()
2356
preview = TransformPreview(revision_tree)
2357
self.addCleanup(preview.finalize)
2358
preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
2359
preview_tree = preview.get_preview_tree()
2361
show_diff_trees(revision_tree, preview_tree, out)
2362
lines = out.getvalue().splitlines()
2363
self.assertEqual(lines[0], "=== added file 'file2'")
2364
# 3 lines of diff administrivia
2365
self.assertEqual(lines[4], "+content B")
2367
def test_transform_conflicts(self):
2368
revision_tree = self.create_tree()
2369
preview = TransformPreview(revision_tree)
2370
self.addCleanup(preview.finalize)
2371
preview.new_file('a', preview.root, 'content 2')
2372
resolve_conflicts(preview)
2373
trans_id = preview.trans_id_file_id('a-id')
2374
self.assertEqual('a.moved', preview.final_name(trans_id))
2376
def get_tree_and_preview_tree(self):
2377
revision_tree = self.create_tree()
2378
preview = TransformPreview(revision_tree)
2379
self.addCleanup(preview.finalize)
2380
a_trans_id = preview.trans_id_file_id('a-id')
2381
preview.delete_contents(a_trans_id)
2382
preview.create_file('b content', a_trans_id)
2383
preview_tree = preview.get_preview_tree()
2384
return revision_tree, preview_tree
2386
def test_iter_changes(self):
2387
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2388
root = revision_tree.inventory.root.file_id
2389
self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
2390
(root, root), ('a', 'a'), ('file', 'file'),
2392
list(preview_tree.iter_changes(revision_tree)))
2394
def test_include_unchanged_succeeds(self):
2395
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2396
changes = preview_tree.iter_changes(revision_tree,
2397
include_unchanged=True)
2398
root = revision_tree.inventory.root.file_id
2400
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2402
def test_specific_files(self):
2403
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2404
changes = preview_tree.iter_changes(revision_tree,
2405
specific_files=[''])
2406
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2408
def test_want_unversioned(self):
2409
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2410
changes = preview_tree.iter_changes(revision_tree,
2411
want_unversioned=True)
2412
self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
2414
def test_ignore_extra_trees_no_specific_files(self):
2415
# extra_trees is harmless without specific_files, so we'll silently
2416
# accept it, even though we won't use it.
2417
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2418
preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
2420
def test_ignore_require_versioned_no_specific_files(self):
2421
# require_versioned is meaningless without specific_files.
2422
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2423
preview_tree.iter_changes(revision_tree, require_versioned=False)
2425
def test_ignore_pb(self):
2426
# pb could be supported, but TT.iter_changes doesn't support it.
2427
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2428
preview_tree.iter_changes(revision_tree)
2430
def test_kind(self):
2431
revision_tree = self.create_tree()
2432
preview = TransformPreview(revision_tree)
2433
self.addCleanup(preview.finalize)
2434
preview.new_file('file', preview.root, 'contents', 'file-id')
2435
preview.new_directory('directory', preview.root, 'dir-id')
2436
preview_tree = preview.get_preview_tree()
2437
self.assertEqual('file', preview_tree.kind('file-id'))
2438
self.assertEqual('directory', preview_tree.kind('dir-id'))
2440
def test_get_file_mtime(self):
2441
preview = self.get_empty_preview()
2442
file_trans_id = preview.new_file('file', preview.root, 'contents',
2444
limbo_path = preview._limbo_name(file_trans_id)
2445
preview_tree = preview.get_preview_tree()
2446
self.assertEqual(os.stat(limbo_path).st_mtime,
2447
preview_tree.get_file_mtime('file-id'))
2449
def test_get_file_mtime_renamed(self):
2450
work_tree = self.make_branch_and_tree('tree')
2451
self.build_tree(['tree/file'])
2452
work_tree.add('file', 'file-id')
2453
preview = TransformPreview(work_tree)
2454
self.addCleanup(preview.finalize)
2455
file_trans_id = preview.trans_id_tree_file_id('file-id')
2456
preview.adjust_path('renamed', preview.root, file_trans_id)
2457
preview_tree = preview.get_preview_tree()
2458
preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
2459
work_mtime = work_tree.get_file_mtime('file-id', 'file')
2461
def test_get_file(self):
2462
preview = self.get_empty_preview()
2463
preview.new_file('file', preview.root, 'contents', 'file-id')
2464
preview_tree = preview.get_preview_tree()
2465
tree_file = preview_tree.get_file('file-id')
2467
self.assertEqual('contents', tree_file.read())
2471
def test_get_symlink_target(self):
2472
self.requireFeature(SymlinkFeature)
2473
preview = self.get_empty_preview()
2474
preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2475
preview_tree = preview.get_preview_tree()
2476
self.assertEqual('target',
2477
preview_tree.get_symlink_target('symlink-id'))
2479
def test_all_file_ids(self):
2480
tree = self.make_branch_and_tree('tree')
2481
self.build_tree(['tree/a', 'tree/b', 'tree/c'])
2482
tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
2483
preview = TransformPreview(tree)
2484
self.addCleanup(preview.finalize)
2485
preview.unversion_file(preview.trans_id_file_id('b-id'))
2486
c_trans_id = preview.trans_id_file_id('c-id')
2487
preview.unversion_file(c_trans_id)
2488
preview.version_file('c-id', c_trans_id)
2489
preview_tree = preview.get_preview_tree()
2490
self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
2491
preview_tree.all_file_ids())
2493
def test_path2id_deleted_unchanged(self):
2494
tree = self.make_branch_and_tree('tree')
2495
self.build_tree(['tree/unchanged', 'tree/deleted'])
2496
tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
2497
preview = TransformPreview(tree)
2498
self.addCleanup(preview.finalize)
2499
preview.unversion_file(preview.trans_id_file_id('deleted-id'))
2500
preview_tree = preview.get_preview_tree()
2501
self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
2502
self.assertIs(None, preview_tree.path2id('deleted'))
2504
def test_path2id_created(self):
2505
tree = self.make_branch_and_tree('tree')
2506
self.build_tree(['tree/unchanged'])
2507
tree.add(['unchanged'], ['unchanged-id'])
2508
preview = TransformPreview(tree)
2509
self.addCleanup(preview.finalize)
2510
preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
2511
'contents', 'new-id')
2512
preview_tree = preview.get_preview_tree()
2513
self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
2515
def test_path2id_moved(self):
2516
tree = self.make_branch_and_tree('tree')
2517
self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
2518
tree.add(['old_parent', 'old_parent/child'],
2519
['old_parent-id', 'child-id'])
2520
preview = TransformPreview(tree)
2521
self.addCleanup(preview.finalize)
2522
new_parent = preview.new_directory('new_parent', preview.root,
2524
preview.adjust_path('child', new_parent,
2525
preview.trans_id_file_id('child-id'))
2526
preview_tree = preview.get_preview_tree()
2527
self.assertIs(None, preview_tree.path2id('old_parent/child'))
2528
self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
2530
def test_path2id_renamed_parent(self):
2531
tree = self.make_branch_and_tree('tree')
2532
self.build_tree(['tree/old_name/', 'tree/old_name/child'])
2533
tree.add(['old_name', 'old_name/child'],
2534
['parent-id', 'child-id'])
2535
preview = TransformPreview(tree)
2536
self.addCleanup(preview.finalize)
2537
preview.adjust_path('new_name', preview.root,
2538
preview.trans_id_file_id('parent-id'))
2539
preview_tree = preview.get_preview_tree()
2540
self.assertIs(None, preview_tree.path2id('old_name/child'))
2541
self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
2543
def assertMatchingIterEntries(self, tt, specific_file_ids=None):
2544
preview_tree = tt.get_preview_tree()
2545
preview_result = list(preview_tree.iter_entries_by_dir(
2549
actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
2550
self.assertEqual(actual_result, preview_result)
2552
def test_iter_entries_by_dir_new(self):
2553
tree = self.make_branch_and_tree('tree')
2554
tt = TreeTransform(tree)
2555
tt.new_file('new', tt.root, 'contents', 'new-id')
2556
self.assertMatchingIterEntries(tt)
2558
def test_iter_entries_by_dir_deleted(self):
2559
tree = self.make_branch_and_tree('tree')
2560
self.build_tree(['tree/deleted'])
2561
tree.add('deleted', 'deleted-id')
2562
tt = TreeTransform(tree)
2563
tt.delete_contents(tt.trans_id_file_id('deleted-id'))
2564
self.assertMatchingIterEntries(tt)
2566
def test_iter_entries_by_dir_unversioned(self):
2567
tree = self.make_branch_and_tree('tree')
2568
self.build_tree(['tree/removed'])
2569
tree.add('removed', 'removed-id')
2570
tt = TreeTransform(tree)
2571
tt.unversion_file(tt.trans_id_file_id('removed-id'))
2572
self.assertMatchingIterEntries(tt)
2574
def test_iter_entries_by_dir_moved(self):
2575
tree = self.make_branch_and_tree('tree')
2576
self.build_tree(['tree/moved', 'tree/new_parent/'])
2577
tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
2578
tt = TreeTransform(tree)
2579
tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
2580
tt.trans_id_file_id('moved-id'))
2581
self.assertMatchingIterEntries(tt)
2583
def test_iter_entries_by_dir_specific_file_ids(self):
2584
tree = self.make_branch_and_tree('tree')
2585
tree.set_root_id('tree-root-id')
2586
self.build_tree(['tree/parent/', 'tree/parent/child'])
2587
tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
2588
tt = TreeTransform(tree)
2589
self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
2591
def test_symlink_content_summary(self):
2592
self.requireFeature(SymlinkFeature)
2593
preview = self.get_empty_preview()
2594
preview.new_symlink('path', preview.root, 'target', 'path-id')
2595
summary = preview.get_preview_tree().path_content_summary('path')
2596
self.assertEqual(('symlink', None, None, 'target'), summary)
2598
def test_missing_content_summary(self):
2599
preview = self.get_empty_preview()
2600
summary = preview.get_preview_tree().path_content_summary('path')
2601
self.assertEqual(('missing', None, None, None), summary)
2603
def test_deleted_content_summary(self):
2604
tree = self.make_branch_and_tree('tree')
2605
self.build_tree(['tree/path/'])
2607
preview = TransformPreview(tree)
2608
self.addCleanup(preview.finalize)
2609
preview.delete_contents(preview.trans_id_tree_path('path'))
2610
summary = preview.get_preview_tree().path_content_summary('path')
2611
self.assertEqual(('missing', None, None, None), summary)
2613
def test_file_content_summary_executable(self):
2614
preview = self.get_empty_preview()
2615
path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2616
preview.set_executability(True, path_id)
2617
summary = preview.get_preview_tree().path_content_summary('path')
2618
self.assertEqual(4, len(summary))
2619
self.assertEqual('file', summary[0])
2620
# size must be known
2621
self.assertEqual(len('contents'), summary[1])
2623
self.assertEqual(True, summary[2])
2624
# will not have hash (not cheap to determine)
2625
self.assertIs(None, summary[3])
2627
def test_change_executability(self):
2628
tree = self.make_branch_and_tree('tree')
2629
self.build_tree(['tree/path'])
2631
preview = TransformPreview(tree)
2632
self.addCleanup(preview.finalize)
2633
path_id = preview.trans_id_tree_path('path')
2634
preview.set_executability(True, path_id)
2635
summary = preview.get_preview_tree().path_content_summary('path')
2636
self.assertEqual(True, summary[2])
2638
def test_file_content_summary_non_exec(self):
2639
preview = self.get_empty_preview()
2640
preview.new_file('path', preview.root, 'contents', 'path-id')
2641
summary = preview.get_preview_tree().path_content_summary('path')
2642
self.assertEqual(4, len(summary))
2643
self.assertEqual('file', summary[0])
2644
# size must be known
2645
self.assertEqual(len('contents'), summary[1])
2647
self.assertEqual(False, summary[2])
2648
# will not have hash (not cheap to determine)
2649
self.assertIs(None, summary[3])
2651
def test_dir_content_summary(self):
2652
preview = self.get_empty_preview()
2653
preview.new_directory('path', preview.root, 'path-id')
2654
summary = preview.get_preview_tree().path_content_summary('path')
2655
self.assertEqual(('directory', None, None, None), summary)
2657
def test_tree_content_summary(self):
2658
preview = self.get_empty_preview()
2659
path = preview.new_directory('path', preview.root, 'path-id')
2660
preview.set_tree_reference('rev-1', path)
2661
summary = preview.get_preview_tree().path_content_summary('path')
2662
self.assertEqual(4, len(summary))
2663
self.assertEqual('tree-reference', summary[0])
2665
def test_annotate(self):
2666
tree = self.make_branch_and_tree('tree')
2667
self.build_tree_contents([('tree/file', 'a\n')])
2668
tree.add('file', 'file-id')
2669
tree.commit('a', rev_id='one')
2670
self.build_tree_contents([('tree/file', 'a\nb\n')])
2671
preview = TransformPreview(tree)
2672
self.addCleanup(preview.finalize)
2673
file_trans_id = preview.trans_id_file_id('file-id')
2674
preview.delete_contents(file_trans_id)
2675
preview.create_file('a\nb\nc\n', file_trans_id)
2676
preview_tree = preview.get_preview_tree()
2682
annotation = preview_tree.annotate_iter('file-id', 'me:')
2683
self.assertEqual(expected, annotation)
2685
def test_annotate_missing(self):
2686
preview = self.get_empty_preview()
2687
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2688
preview_tree = preview.get_preview_tree()
2694
annotation = preview_tree.annotate_iter('file-id', 'me:')
2695
self.assertEqual(expected, annotation)
2697
def test_annotate_rename(self):
2698
tree = self.make_branch_and_tree('tree')
2699
self.build_tree_contents([('tree/file', 'a\n')])
2700
tree.add('file', 'file-id')
2701
tree.commit('a', rev_id='one')
2702
preview = TransformPreview(tree)
2703
self.addCleanup(preview.finalize)
2704
file_trans_id = preview.trans_id_file_id('file-id')
2705
preview.adjust_path('newname', preview.root, file_trans_id)
2706
preview_tree = preview.get_preview_tree()
2710
annotation = preview_tree.annotate_iter('file-id', 'me:')
2711
self.assertEqual(expected, annotation)
2713
def test_annotate_deleted(self):
2714
tree = self.make_branch_and_tree('tree')
2715
self.build_tree_contents([('tree/file', 'a\n')])
2716
tree.add('file', 'file-id')
2717
tree.commit('a', rev_id='one')
2718
self.build_tree_contents([('tree/file', 'a\nb\n')])
2719
preview = TransformPreview(tree)
2720
self.addCleanup(preview.finalize)
2721
file_trans_id = preview.trans_id_file_id('file-id')
2722
preview.delete_contents(file_trans_id)
2723
preview_tree = preview.get_preview_tree()
2724
annotation = preview_tree.annotate_iter('file-id', 'me:')
2725
self.assertIs(None, annotation)
2727
def test_stored_kind(self):
2728
preview = self.get_empty_preview()
2729
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2730
preview_tree = preview.get_preview_tree()
2731
self.assertEqual('file', preview_tree.stored_kind('file-id'))
2733
def test_is_executable(self):
2734
preview = self.get_empty_preview()
2735
preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
2736
preview.set_executability(True, preview.trans_id_file_id('file-id'))
2737
preview_tree = preview.get_preview_tree()
2738
self.assertEqual(True, preview_tree.is_executable('file-id'))
2740
def test_get_set_parent_ids(self):
2741
revision_tree, preview_tree = self.get_tree_and_preview_tree()
2742
self.assertEqual([], preview_tree.get_parent_ids())
2743
preview_tree.set_parent_ids(['rev-1'])
2744
self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
2746
def test_plan_file_merge(self):
2747
work_a = self.make_branch_and_tree('wta')
2748
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2749
work_a.add('file', 'file-id')
2750
base_id = work_a.commit('base version')
2751
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2752
preview = TransformPreview(work_a)
2753
self.addCleanup(preview.finalize)
2754
trans_id = preview.trans_id_file_id('file-id')
2755
preview.delete_contents(trans_id)
2756
preview.create_file('b\nc\nd\ne\n', trans_id)
2757
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2758
tree_a = preview.get_preview_tree()
2759
tree_a.set_parent_ids([base_id])
2761
('killed-a', 'a\n'),
2762
('killed-b', 'b\n'),
2763
('unchanged', 'c\n'),
2764
('unchanged', 'd\n'),
2767
], list(tree_a.plan_file_merge('file-id', tree_b)))
2769
def test_plan_file_merge_revision_tree(self):
2770
work_a = self.make_branch_and_tree('wta')
2771
self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
2772
work_a.add('file', 'file-id')
2773
base_id = work_a.commit('base version')
2774
tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
2775
preview = TransformPreview(work_a.basis_tree())
2776
self.addCleanup(preview.finalize)
2777
trans_id = preview.trans_id_file_id('file-id')
2778
preview.delete_contents(trans_id)
2779
preview.create_file('b\nc\nd\ne\n', trans_id)
2780
self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
2781
tree_a = preview.get_preview_tree()
2782
tree_a.set_parent_ids([base_id])
2784
('killed-a', 'a\n'),
2785
('killed-b', 'b\n'),
2786
('unchanged', 'c\n'),
2787
('unchanged', 'd\n'),
2790
], list(tree_a.plan_file_merge('file-id', tree_b)))
2792
def test_walkdirs(self):
2793
preview = self.get_empty_preview()
2794
root = preview.new_directory('', ROOT_PARENT, 'tree-root')
2795
# FIXME: new_directory should mark root.
2796
preview.fixup_new_roots()
2797
preview_tree = preview.get_preview_tree()
2798
file_trans_id = preview.new_file('a', preview.root, 'contents',
2800
expected = [(('', 'tree-root'),
2801
[('a', 'a', 'file', None, 'a-id', 'file')])]
2802
self.assertEqual(expected, list(preview_tree.walkdirs()))
2804
def test_extras(self):
2805
work_tree = self.make_branch_and_tree('tree')
2806
self.build_tree(['tree/removed-file', 'tree/existing-file',
2807
'tree/not-removed-file'])
2808
work_tree.add(['removed-file', 'not-removed-file'])
2809
preview = TransformPreview(work_tree)
2810
self.addCleanup(preview.finalize)
2811
preview.new_file('new-file', preview.root, 'contents')
2812
preview.new_file('new-versioned-file', preview.root, 'contents',
2814
tree = preview.get_preview_tree()
2815
preview.unversion_file(preview.trans_id_tree_path('removed-file'))
2816
self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
2819
def test_merge_into_preview(self):
2820
work_tree = self.make_branch_and_tree('tree')
2821
self.build_tree_contents([('tree/file','b\n')])
2822
work_tree.add('file', 'file-id')
2823
work_tree.commit('first commit')
2824
child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
2825
self.build_tree_contents([('child/file','b\nc\n')])
2826
child_tree.commit('child commit')
2827
child_tree.lock_write()
2828
self.addCleanup(child_tree.unlock)
2829
work_tree.lock_write()
2830
self.addCleanup(work_tree.unlock)
2831
preview = TransformPreview(work_tree)
2832
self.addCleanup(preview.finalize)
2833
file_trans_id = preview.trans_id_file_id('file-id')
2834
preview.delete_contents(file_trans_id)
2835
preview.create_file('a\nb\n', file_trans_id)
2836
preview_tree = preview.get_preview_tree()
2837
merger = Merger.from_revision_ids(None, preview_tree,
2838
child_tree.branch.last_revision(),
2839
other_branch=child_tree.branch,
2840
tree_branch=work_tree.branch)
2841
merger.merge_type = Merge3Merger
2842
tt = merger.make_merger().make_preview_transform()
2843
self.addCleanup(tt.finalize)
2844
final_tree = tt.get_preview_tree()
2845
self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
2847
def test_merge_preview_into_workingtree(self):
2848
tree = self.make_branch_and_tree('tree')
2849
tree.set_root_id('TREE_ROOT')
2850
tt = TransformPreview(tree)
2851
self.addCleanup(tt.finalize)
2852
tt.new_file('name', tt.root, 'content', 'file-id')
2853
tree2 = self.make_branch_and_tree('tree2')
2854
tree2.set_root_id('TREE_ROOT')
2855
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2856
None, tree.basis_tree())
2857
merger.merge_type = Merge3Merger
2860
def test_merge_preview_into_workingtree_handles_conflicts(self):
2861
tree = self.make_branch_and_tree('tree')
2862
self.build_tree_contents([('tree/foo', 'bar')])
2863
tree.add('foo', 'foo-id')
2865
tt = TransformPreview(tree)
2866
self.addCleanup(tt.finalize)
2867
trans_id = tt.trans_id_file_id('foo-id')
2868
tt.delete_contents(trans_id)
2869
tt.create_file('baz', trans_id)
2870
tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2871
self.build_tree_contents([('tree2/foo', 'qux')])
2873
merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2874
pb, tree.basis_tree())
2875
merger.merge_type = Merge3Merger
2878
def test_is_executable(self):
2879
tree = self.make_branch_and_tree('tree')
2880
preview = TransformPreview(tree)
2881
self.addCleanup(preview.finalize)
2882
preview.new_file('foo', preview.root, 'bar', 'baz-id')
2883
preview_tree = preview.get_preview_tree()
2884
self.assertEqual(False, preview_tree.is_executable('baz-id',
2886
self.assertEqual(False, preview_tree.is_executable('baz-id'))
2888
def test_commit_preview_tree(self):
2889
tree = self.make_branch_and_tree('tree')
2890
rev_id = tree.commit('rev1')
2891
tree.branch.lock_write()
2892
self.addCleanup(tree.branch.unlock)
2893
tt = TransformPreview(tree)
2894
tt.new_file('file', tt.root, 'contents', 'file_id')
2895
self.addCleanup(tt.finalize)
2896
preview = tt.get_preview_tree()
2897
preview.set_parent_ids([rev_id])
2898
builder = tree.branch.get_commit_builder([rev_id])
2899
list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
2900
builder.finish_inventory()
2901
rev2_id = builder.commit('rev2')
2902
rev2_tree = tree.branch.repository.revision_tree(rev2_id)
2903
self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
2905
def test_ascii_limbo_paths(self):
2906
self.requireFeature(tests.UnicodeFilenameFeature)
2907
branch = self.make_branch('any')
2908
tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
2909
tt = TransformPreview(tree)
2910
self.addCleanup(tt.finalize)
2911
foo_id = tt.new_directory('', ROOT_PARENT)
2912
bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
2913
limbo_path = tt._limbo_name(bar_id)
2914
self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
2917
class FakeSerializer(object):
2918
"""Serializer implementation that simply returns the input.
2920
The input is returned in the order used by pack.ContainerPushParser.
2923
def bytes_record(bytes, names):
2927
class TestSerializeTransform(tests.TestCaseWithTransport):
2929
_test_needs_features = [tests.UnicodeFilenameFeature]
2931
def get_preview(self, tree=None):
2933
tree = self.make_branch_and_tree('tree')
2934
tt = TransformPreview(tree)
2935
self.addCleanup(tt.finalize)
2938
def assertSerializesTo(self, expected, tt):
2939
records = list(tt.serialize(FakeSerializer()))
2940
self.assertEqual(expected, records)
2943
def default_attribs():
2948
'_new_executability': {},
2950
'_tree_path_ids': {'': 'new-0'},
2952
'_removed_contents': [],
2953
'_non_present_ids': {},
2956
def make_records(self, attribs, contents):
2958
(((('attribs'),),), bencode.bencode(attribs))]
2959
records.extend([(((n, k),), c) for n, k, c in contents])
2962
def creation_records(self):
2963
attribs = self.default_attribs()
2964
attribs['_id_number'] = 3
2965
attribs['_new_name'] = {
2966
'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
2967
attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
2968
attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
2969
attribs['_new_executability'] = {'new-1': 1}
2971
('new-1', 'file', 'i 1\nbar\n'),
2972
('new-2', 'directory', ''),
2974
return self.make_records(attribs, contents)
2976
def test_serialize_creation(self):
2977
tt = self.get_preview()
2978
tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
2979
tt.new_directory('qux', tt.root, 'quxx')
2980
self.assertSerializesTo(self.creation_records(), tt)
2982
def test_deserialize_creation(self):
2983
tt = self.get_preview()
2984
tt.deserialize(iter(self.creation_records()))
2985
self.assertEqual(3, tt._id_number)
2986
self.assertEqual({'new-1': u'foo\u1234',
2987
'new-2': 'qux'}, tt._new_name)
2988
self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
2989
self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
2990
self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
2991
self.assertEqual({'new-1': True}, tt._new_executability)
2992
self.assertEqual({'new-1': 'file',
2993
'new-2': 'directory'}, tt._new_contents)
2994
foo_limbo = open(tt._limbo_name('new-1'), 'rb')
2996
foo_content = foo_limbo.read()
2999
self.assertEqual('bar', foo_content)
3001
def symlink_creation_records(self):
3002
attribs = self.default_attribs()
3003
attribs['_id_number'] = 2
3004
attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
3005
attribs['_new_parent'] = {'new-1': 'new-0'}
3006
contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
3007
return self.make_records(attribs, contents)
3009
def test_serialize_symlink_creation(self):
3010
self.requireFeature(tests.SymlinkFeature)
3011
tt = self.get_preview()
3012
tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
3013
self.assertSerializesTo(self.symlink_creation_records(), tt)
3015
def test_deserialize_symlink_creation(self):
3016
self.requireFeature(tests.SymlinkFeature)
3017
tt = self.get_preview()
3018
tt.deserialize(iter(self.symlink_creation_records()))
3019
abspath = tt._limbo_name('new-1')
3020
foo_content = osutils.readlink(abspath)
3021
self.assertEqual(u'bar\u1234', foo_content)
3023
def make_destruction_preview(self):
3024
tree = self.make_branch_and_tree('.')
3025
self.build_tree([u'foo\u1234', 'bar'])
3026
tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
3027
return self.get_preview(tree)
3029
def destruction_records(self):
3030
attribs = self.default_attribs()
3031
attribs['_id_number'] = 3
3032
attribs['_removed_id'] = ['new-1']
3033
attribs['_removed_contents'] = ['new-2']
3034
attribs['_tree_path_ids'] = {
3036
u'foo\u1234'.encode('utf-8'): 'new-1',
3039
return self.make_records(attribs, [])
3041
def test_serialize_destruction(self):
3042
tt = self.make_destruction_preview()
3043
foo_trans_id = tt.trans_id_tree_file_id('foo-id')
3044
tt.unversion_file(foo_trans_id)
3045
bar_trans_id = tt.trans_id_tree_file_id('bar-id')
3046
tt.delete_contents(bar_trans_id)
3047
self.assertSerializesTo(self.destruction_records(), tt)
3049
def test_deserialize_destruction(self):
3050
tt = self.make_destruction_preview()
3051
tt.deserialize(iter(self.destruction_records()))
3052
self.assertEqual({u'foo\u1234': 'new-1',
3054
'': tt.root}, tt._tree_path_ids)
3055
self.assertEqual({'new-1': u'foo\u1234',
3057
tt.root: ''}, tt._tree_id_paths)
3058
self.assertEqual(set(['new-1']), tt._removed_id)
3059
self.assertEqual(set(['new-2']), tt._removed_contents)
3061
def missing_records(self):
3062
attribs = self.default_attribs()
3063
attribs['_id_number'] = 2
3064
attribs['_non_present_ids'] = {
3066
return self.make_records(attribs, [])
3068
def test_serialize_missing(self):
3069
tt = self.get_preview()
3070
boo_trans_id = tt.trans_id_file_id('boo')
3071
self.assertSerializesTo(self.missing_records(), tt)
3073
def test_deserialize_missing(self):
3074
tt = self.get_preview()
3075
tt.deserialize(iter(self.missing_records()))
3076
self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
3078
def make_modification_preview(self):
3079
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3080
LINES_TWO = 'z\nbb\nx\ndd\n'
3081
tree = self.make_branch_and_tree('tree')
3082
self.build_tree_contents([('tree/file', LINES_ONE)])
3083
tree.add('file', 'file-id')
3084
return self.get_preview(tree), LINES_TWO
3086
def modification_records(self):
3087
attribs = self.default_attribs()
3088
attribs['_id_number'] = 2
3089
attribs['_tree_path_ids'] = {
3092
attribs['_removed_contents'] = ['new-1']
3093
contents = [('new-1', 'file',
3094
'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
3095
return self.make_records(attribs, contents)
3097
def test_serialize_modification(self):
3098
tt, LINES = self.make_modification_preview()
3099
trans_id = tt.trans_id_file_id('file-id')
3100
tt.delete_contents(trans_id)
3101
tt.create_file(LINES, trans_id)
3102
self.assertSerializesTo(self.modification_records(), tt)
3104
def test_deserialize_modification(self):
3105
tt, LINES = self.make_modification_preview()
3106
tt.deserialize(iter(self.modification_records()))
3107
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3109
def make_kind_change_preview(self):
3110
LINES = 'a\nb\nc\nd\n'
3111
tree = self.make_branch_and_tree('tree')
3112
self.build_tree(['tree/foo/'])
3113
tree.add('foo', 'foo-id')
3114
return self.get_preview(tree), LINES
3116
def kind_change_records(self):
3117
attribs = self.default_attribs()
3118
attribs['_id_number'] = 2
3119
attribs['_tree_path_ids'] = {
3122
attribs['_removed_contents'] = ['new-1']
3123
contents = [('new-1', 'file',
3124
'i 4\na\nb\nc\nd\n\n')]
3125
return self.make_records(attribs, contents)
3127
def test_serialize_kind_change(self):
3128
tt, LINES = self.make_kind_change_preview()
3129
trans_id = tt.trans_id_file_id('foo-id')
3130
tt.delete_contents(trans_id)
3131
tt.create_file(LINES, trans_id)
3132
self.assertSerializesTo(self.kind_change_records(), tt)
3134
def test_deserialize_kind_change(self):
3135
tt, LINES = self.make_kind_change_preview()
3136
tt.deserialize(iter(self.kind_change_records()))
3137
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3139
def make_add_contents_preview(self):
3140
LINES = 'a\nb\nc\nd\n'
3141
tree = self.make_branch_and_tree('tree')
3142
self.build_tree(['tree/foo'])
3144
os.unlink('tree/foo')
3145
return self.get_preview(tree), LINES
3147
def add_contents_records(self):
3148
attribs = self.default_attribs()
3149
attribs['_id_number'] = 2
3150
attribs['_tree_path_ids'] = {
3153
contents = [('new-1', 'file',
3154
'i 4\na\nb\nc\nd\n\n')]
3155
return self.make_records(attribs, contents)
3157
def test_serialize_add_contents(self):
3158
tt, LINES = self.make_add_contents_preview()
3159
trans_id = tt.trans_id_tree_path('foo')
3160
tt.create_file(LINES, trans_id)
3161
self.assertSerializesTo(self.add_contents_records(), tt)
3163
def test_deserialize_add_contents(self):
3164
tt, LINES = self.make_add_contents_preview()
3165
tt.deserialize(iter(self.add_contents_records()))
3166
self.assertFileEqual(LINES, tt._limbo_name('new-1'))
3168
def test_get_parents_lines(self):
3169
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3170
LINES_TWO = 'z\nbb\nx\ndd\n'
3171
tree = self.make_branch_and_tree('tree')
3172
self.build_tree_contents([('tree/file', LINES_ONE)])
3173
tree.add('file', 'file-id')
3174
tt = self.get_preview(tree)
3175
trans_id = tt.trans_id_tree_path('file')
3176
self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
3177
tt._get_parents_lines(trans_id))
3179
def test_get_parents_texts(self):
3180
LINES_ONE = 'aa\nbb\ncc\ndd\n'
3181
LINES_TWO = 'z\nbb\nx\ndd\n'
3182
tree = self.make_branch_and_tree('tree')
3183
self.build_tree_contents([('tree/file', LINES_ONE)])
3184
tree.add('file', 'file-id')
3185
tt = self.get_preview(tree)
3186
trans_id = tt.trans_id_tree_path('file')
3187
self.assertEqual((LINES_ONE,),
3188
tt._get_parents_texts(trans_id))