775
688
# This will unlock it
776
689
self.check_state_with_reopen(expected_result, state)
778
def test_set_state_from_scratch_no_parents(self):
779
tree1, revid1 = self.make_minimal_tree()
780
inv = tree1.inventory
781
root_id = inv.path2id('')
782
expected_result = [], [
783
(('', '', root_id), [
784
('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
785
state = dirstate.DirState.initialize('dirstate')
787
state.set_state_from_scratch(inv, [], [])
788
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
790
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
791
state._dirblock_state)
796
# This will unlock it
797
self.check_state_with_reopen(expected_result, state)
799
def test_set_state_from_scratch_identical_parent(self):
800
tree1, revid1 = self.make_minimal_tree()
801
inv = tree1.inventory
802
root_id = inv.path2id('')
803
rev_tree1 = tree1.branch.repository.revision_tree(revid1)
804
d_entry = ('d', '', 0, False, dirstate.DirState.NULLSTAT)
805
parent_entry = ('d', '', 0, False, revid1)
806
expected_result = [revid1], [
807
(('', '', root_id), [d_entry, parent_entry])]
808
state = dirstate.DirState.initialize('dirstate')
810
state.set_state_from_scratch(inv, [(revid1, rev_tree1)], [])
811
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
813
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
814
state._dirblock_state)
819
# This will unlock it
820
self.check_state_with_reopen(expected_result, state)
822
def test_set_state_from_inventory_preserves_hashcache(self):
823
# https://bugs.launchpad.net/bzr/+bug/146176
824
# set_state_from_inventory should preserve the stat and hash value for
825
# workingtree files that are not changed by the inventory.
827
tree = self.make_branch_and_tree('.')
828
# depends on the default format using dirstate...
831
# make a dirstate with some valid hashcache data
832
# file on disk, but that's not needed for this test
833
foo_contents = 'contents of foo'
834
self.build_tree_contents([('foo', foo_contents)])
835
tree.add('foo', 'foo-id')
837
foo_stat = os.stat('foo')
838
foo_packed = dirstate.pack_stat(foo_stat)
839
foo_sha = osutils.sha_string(foo_contents)
840
foo_size = len(foo_contents)
842
# should not be cached yet, because the file's too fresh
844
(('', 'foo', 'foo-id',),
845
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
846
tree._dirstate._get_entry(0, 'foo-id'))
847
# poke in some hashcache information - it wouldn't normally be
848
# stored because it's too fresh
849
tree._dirstate.update_minimal(
850
('', 'foo', 'foo-id'),
851
'f', False, foo_sha, foo_packed, foo_size, 'foo')
852
# now should be cached
854
(('', 'foo', 'foo-id',),
855
[('f', foo_sha, foo_size, False, foo_packed)]),
856
tree._dirstate._get_entry(0, 'foo-id'))
858
# extract the inventory, and add something to it
859
inv = tree._get_inventory()
860
# should see the file we poked in...
861
self.assertTrue(inv.has_id('foo-id'))
862
self.assertTrue(inv.has_filename('foo'))
863
inv.add_path('bar', 'file', 'bar-id')
864
tree._dirstate._validate()
865
# this used to cause it to lose its hashcache
866
tree._dirstate.set_state_from_inventory(inv)
867
tree._dirstate._validate()
873
# now check that the state still has the original hashcache value
874
state = tree._dirstate
876
foo_tuple = state._get_entry(0, path_utf8='foo')
878
(('', 'foo', 'foo-id',),
879
[('f', foo_sha, len(foo_contents), False,
880
dirstate.pack_stat(foo_stat))]),
885
def test_set_state_from_inventory_mixed_paths(self):
886
tree1 = self.make_branch_and_tree('tree1')
887
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
888
'tree1/a/b/foo', 'tree1/a-b/bar'])
891
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
892
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
893
tree1.commit('rev1', rev_id='rev1')
894
root_id = tree1.get_root_id()
895
inv = tree1.inventory
898
expected_result1 = [('', '', root_id, 'd'),
899
('', 'a', 'a-id', 'd'),
900
('', 'a-b', 'a-b-id', 'd'),
901
('a', 'b', 'b-id', 'd'),
902
('a/b', 'foo', 'foo-id', 'f'),
903
('a-b', 'bar', 'bar-id', 'f'),
905
expected_result2 = [('', '', root_id, 'd'),
906
('', 'a', 'a-id', 'd'),
907
('', 'a-b', 'a-b-id', 'd'),
908
('a-b', 'bar', 'bar-id', 'f'),
910
state = dirstate.DirState.initialize('dirstate')
912
state.set_state_from_inventory(inv)
914
for entry in state._iter_entries():
915
values.append(entry[0] + entry[1][0][:1])
916
self.assertEqual(expected_result1, values)
918
state.set_state_from_inventory(inv)
920
for entry in state._iter_entries():
921
values.append(entry[0] + entry[1][0][:1])
922
self.assertEqual(expected_result2, values)
926
691
def test_set_path_id_no_parents(self):
927
692
"""The id of a path can be changed trivally with no parents."""
928
693
state = dirstate.DirState.initialize('dirstate')
930
695
# check precondition to be sure the state does change appropriately.
931
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
932
self.assertEqual([root_entry], list(state._iter_entries()))
933
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
934
self.assertEqual(root_entry,
935
state._get_entry(0, fileid_utf8='TREE_ROOT'))
936
self.assertEqual((None, None),
937
state._get_entry(0, fileid_utf8='second-root-id'))
938
state.set_path_id('', 'second-root-id')
939
new_root_entry = (('', '', 'second-root-id'),
940
[('d', '', 0, False, 'x'*32)])
941
expected_rows = [new_root_entry]
697
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
698
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
699
list(state._iter_entries()))
700
state.set_path_id('', 'foobarbaz')
702
(('', '', 'foobarbaz'), [('d', '', 0, False,
703
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
942
704
self.assertEqual(expected_rows, list(state._iter_entries()))
943
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
944
self.assertEqual(new_root_entry,
945
state._get_entry(0, fileid_utf8='second-root-id'))
946
self.assertEqual((None, None),
947
state._get_entry(0, fileid_utf8='TREE_ROOT'))
948
705
# should work across save too
1623
class TestIterChildEntries(TestCaseWithDirState):
1625
def create_dirstate_with_two_trees(self):
1626
"""This dirstate contains multiple files and directories.
1636
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1638
Notice that a/e is an empty directory.
1640
There is one parent tree, which has the same shape with the following variations:
1641
b/g in the parent is gone.
1642
b/h in the parent has a different id
1643
b/i is new in the parent
1644
c is renamed to b/j in the parent
1646
:return: The dirstate, still write-locked.
1648
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1649
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1650
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1651
root_entry = ('', '', 'a-root-value'), [
1652
('d', '', 0, False, packed_stat),
1653
('d', '', 0, False, 'parent-revid'),
1655
a_entry = ('', 'a', 'a-dir'), [
1656
('d', '', 0, False, packed_stat),
1657
('d', '', 0, False, 'parent-revid'),
1659
b_entry = ('', 'b', 'b-dir'), [
1660
('d', '', 0, False, packed_stat),
1661
('d', '', 0, False, 'parent-revid'),
1663
c_entry = ('', 'c', 'c-file'), [
1664
('f', null_sha, 10, False, packed_stat),
1665
('r', 'b/j', 0, False, ''),
1667
d_entry = ('', 'd', 'd-file'), [
1668
('f', null_sha, 20, False, packed_stat),
1669
('f', 'd', 20, False, 'parent-revid'),
1671
e_entry = ('a', 'e', 'e-dir'), [
1672
('d', '', 0, False, packed_stat),
1673
('d', '', 0, False, 'parent-revid'),
1675
f_entry = ('a', 'f', 'f-file'), [
1676
('f', null_sha, 30, False, packed_stat),
1677
('f', 'f', 20, False, 'parent-revid'),
1679
g_entry = ('b', 'g', 'g-file'), [
1680
('f', null_sha, 30, False, packed_stat),
1681
NULL_PARENT_DETAILS,
1683
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1684
('f', null_sha, 40, False, packed_stat),
1685
NULL_PARENT_DETAILS,
1687
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1688
NULL_PARENT_DETAILS,
1689
('f', 'h', 20, False, 'parent-revid'),
1691
i_entry = ('b', 'i', 'i-file'), [
1692
NULL_PARENT_DETAILS,
1693
('f', 'h', 20, False, 'parent-revid'),
1695
j_entry = ('b', 'j', 'c-file'), [
1696
('r', 'c', 0, False, ''),
1697
('f', 'j', 20, False, 'parent-revid'),
1700
dirblocks.append(('', [root_entry]))
1701
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1702
dirblocks.append(('a', [e_entry, f_entry]))
1703
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1704
state = dirstate.DirState.initialize('dirstate')
1707
state._set_data(['parent'], dirblocks)
1711
return state, dirblocks
1713
def test_iter_children_b(self):
1714
state, dirblocks = self.create_dirstate_with_two_trees()
1715
self.addCleanup(state.unlock)
1716
expected_result = []
1717
expected_result.append(dirblocks[3][1][2]) # h2
1718
expected_result.append(dirblocks[3][1][3]) # i
1719
expected_result.append(dirblocks[3][1][4]) # j
1720
self.assertEqual(expected_result,
1721
list(state._iter_child_entries(1, 'b')))
1723
def test_iter_child_root(self):
1724
state, dirblocks = self.create_dirstate_with_two_trees()
1725
self.addCleanup(state.unlock)
1726
expected_result = []
1727
expected_result.append(dirblocks[1][1][0]) # a
1728
expected_result.append(dirblocks[1][1][1]) # b
1729
expected_result.append(dirblocks[1][1][3]) # d
1730
expected_result.append(dirblocks[2][1][0]) # e
1731
expected_result.append(dirblocks[2][1][1]) # f
1732
expected_result.append(dirblocks[3][1][2]) # h2
1733
expected_result.append(dirblocks[3][1][3]) # i
1734
expected_result.append(dirblocks[3][1][4]) # j
1735
self.assertEqual(expected_result,
1736
list(state._iter_child_entries(1, '')))
1739
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1280
class TestDirstateSortOrder(TestCaseWithTransport):
1740
1281
"""Test that DirState adds entries in the right order."""
1742
1283
def test_add_sorting(self):
1853
1390
self.st_ino = ino
1854
1391
self.st_mode = mode
1858
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1859
st.st_ino, st.st_mode)
1862
class TestPackStat(tests.TestCaseWithTransport):
1394
class TestUpdateEntry(TestCaseWithDirState):
1395
"""Test the DirState.update_entry functions"""
1397
def get_state_with_a(self):
1398
"""Create a DirState tracking a single object named 'a'"""
1399
state = InstrumentedDirState.initialize('dirstate')
1400
self.addCleanup(state.unlock)
1401
state.add('a', 'a-id', 'file', None, '')
1402
entry = state._get_entry(0, path_utf8='a')
1405
def test_update_entry(self):
1406
state, entry = self.get_state_with_a()
1407
self.build_tree(['a'])
1408
# Add one where we don't provide the stat or sha already
1409
self.assertEqual(('', 'a', 'a-id'), entry[0])
1410
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1412
# Flush the buffers to disk
1414
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1415
state._dirblock_state)
1417
stat_value = os.lstat('a')
1418
packed_stat = dirstate.pack_stat(stat_value)
1419
link_or_sha1 = state.update_entry(entry, abspath='a',
1420
stat_value=stat_value)
1421
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1424
# The dirblock entry should be updated with the new info
1425
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1427
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1428
state._dirblock_state)
1429
mode = stat_value.st_mode
1430
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1433
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1434
state._dirblock_state)
1436
# If we do it again right away, we don't know if the file has changed
1437
# so we will re-read the file. Roll the clock back so the file is
1438
# guaranteed to look too new.
1439
state.adjust_time(-10)
1441
link_or_sha1 = state.update_entry(entry, abspath='a',
1442
stat_value=stat_value)
1443
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1444
('sha1', 'a'), ('is_exec', mode, False),
1446
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1448
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1449
state._dirblock_state)
1452
# However, if we move the clock forward so the file is considered
1453
# "stable", it should just returned the cached value.
1454
state.adjust_time(20)
1455
link_or_sha1 = state.update_entry(entry, abspath='a',
1456
stat_value=stat_value)
1457
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1459
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1460
('sha1', 'a'), ('is_exec', mode, False),
1463
def test_update_entry_no_stat_value(self):
1464
"""Passing the stat_value is optional."""
1465
state, entry = self.get_state_with_a()
1466
state.adjust_time(-10) # Make sure the file looks new
1467
self.build_tree(['a'])
1468
# Add one where we don't provide the stat or sha already
1469
link_or_sha1 = state.update_entry(entry, abspath='a')
1470
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1472
stat_value = os.lstat('a')
1473
self.assertEqual([('lstat', 'a'), ('sha1', 'a'),
1474
('is_exec', stat_value.st_mode, False),
1477
def test_update_entry_symlink(self):
1478
"""Update entry should read symlinks."""
1479
if not osutils.has_symlinks():
1480
# PlatformDeficiency / TestSkipped
1481
raise TestSkipped("No symlink support")
1482
state, entry = self.get_state_with_a()
1484
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1485
state._dirblock_state)
1486
os.symlink('target', 'a')
1488
state.adjust_time(-10) # Make the symlink look new
1489
stat_value = os.lstat('a')
1490
packed_stat = dirstate.pack_stat(stat_value)
1491
link_or_sha1 = state.update_entry(entry, abspath='a',
1492
stat_value=stat_value)
1493
self.assertEqual('target', link_or_sha1)
1494
self.assertEqual([('read_link', 'a', '')], state._log)
1495
# Dirblock is updated
1496
self.assertEqual([('l', link_or_sha1, 6, False, packed_stat)],
1498
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1499
state._dirblock_state)
1501
# Because the stat_value looks new, we should re-read the target
1502
link_or_sha1 = state.update_entry(entry, abspath='a',
1503
stat_value=stat_value)
1504
self.assertEqual('target', link_or_sha1)
1505
self.assertEqual([('read_link', 'a', ''),
1506
('read_link', 'a', 'target'),
1508
state.adjust_time(+20) # Skip into the future, all files look old
1509
link_or_sha1 = state.update_entry(entry, abspath='a',
1510
stat_value=stat_value)
1511
self.assertEqual('target', link_or_sha1)
1512
# There should not be a new read_link call.
1513
# (this is a weak assertion, because read_link is fairly inexpensive,
1514
# versus the number of symlinks that we would have)
1515
self.assertEqual([('read_link', 'a', ''),
1516
('read_link', 'a', 'target'),
1519
def test_update_entry_dir(self):
1520
state, entry = self.get_state_with_a()
1521
self.build_tree(['a/'])
1522
self.assertIs(None, state.update_entry(entry, 'a'))
1524
def create_and_test_file(self, state, entry):
1525
"""Create a file at 'a' and verify the state finds it.
1527
The state should already be versioning *something* at 'a'. This makes
1528
sure that state.update_entry recognizes it as a file.
1530
self.build_tree(['a'])
1531
stat_value = os.lstat('a')
1532
packed_stat = dirstate.pack_stat(stat_value)
1534
link_or_sha1 = state.update_entry(entry, abspath='a')
1535
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1537
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1541
def create_and_test_dir(self, state, entry):
1542
"""Create a directory at 'a' and verify the state finds it.
1544
The state should already be versioning *something* at 'a'. This makes
1545
sure that state.update_entry recognizes it as a directory.
1547
self.build_tree(['a/'])
1548
stat_value = os.lstat('a')
1549
packed_stat = dirstate.pack_stat(stat_value)
1551
link_or_sha1 = state.update_entry(entry, abspath='a')
1552
self.assertIs(None, link_or_sha1)
1553
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1557
def create_and_test_symlink(self, state, entry):
1558
"""Create a symlink at 'a' and verify the state finds it.
1560
The state should already be versioning *something* at 'a'. This makes
1561
sure that state.update_entry recognizes it as a symlink.
1563
This should not be called if this platform does not have symlink
1566
# caller should care about skipping test on platforms without symlinks
1567
os.symlink('path/to/foo', 'a')
1569
stat_value = os.lstat('a')
1570
packed_stat = dirstate.pack_stat(stat_value)
1572
link_or_sha1 = state.update_entry(entry, abspath='a')
1573
self.assertEqual('path/to/foo', link_or_sha1)
1574
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1578
def test_update_missing_file(self):
1579
state, entry = self.get_state_with_a()
1580
packed_stat = self.create_and_test_file(state, entry)
1581
# Now if we delete the file, update_entry should recover and
1584
self.assertIs(None, state.update_entry(entry, abspath='a'))
1585
# And the record shouldn't be changed.
1586
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1587
self.assertEqual([('f', digest, 14, False, packed_stat)],
1590
def test_update_missing_dir(self):
1591
state, entry = self.get_state_with_a()
1592
packed_stat = self.create_and_test_dir(state, entry)
1593
# Now if we delete the directory, update_entry should recover and
1596
self.assertIs(None, state.update_entry(entry, abspath='a'))
1597
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1599
def test_update_missing_symlink(self):
1600
if not osutils.has_symlinks():
1601
# PlatformDeficiency / TestSkipped
1602
raise TestSkipped("No symlink support")
1603
state, entry = self.get_state_with_a()
1604
packed_stat = self.create_and_test_symlink(state, entry)
1606
self.assertIs(None, state.update_entry(entry, abspath='a'))
1607
# And the record shouldn't be changed.
1608
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1611
def test_update_file_to_dir(self):
1612
"""If a file changes to a directory we return None for the sha.
1613
We also update the inventory record.
1615
state, entry = self.get_state_with_a()
1616
self.create_and_test_file(state, entry)
1618
self.create_and_test_dir(state, entry)
1620
def test_update_file_to_symlink(self):
1621
"""File becomes a symlink"""
1622
if not osutils.has_symlinks():
1623
# PlatformDeficiency / TestSkipped
1624
raise TestSkipped("No symlink support")
1625
state, entry = self.get_state_with_a()
1626
self.create_and_test_file(state, entry)
1628
self.create_and_test_symlink(state, entry)
1630
def test_update_dir_to_file(self):
1631
"""Directory becoming a file updates the entry."""
1632
state, entry = self.get_state_with_a()
1633
self.create_and_test_dir(state, entry)
1635
self.create_and_test_file(state, entry)
1637
def test_update_dir_to_symlink(self):
1638
"""Directory becomes a symlink"""
1639
if not osutils.has_symlinks():
1640
# PlatformDeficiency / TestSkipped
1641
raise TestSkipped("No symlink support")
1642
state, entry = self.get_state_with_a()
1643
self.create_and_test_dir(state, entry)
1645
self.create_and_test_symlink(state, entry)
1647
def test_update_symlink_to_file(self):
1648
"""Symlink becomes a file"""
1649
if not has_symlinks():
1650
raise TestSkipped("No symlink support")
1651
state, entry = self.get_state_with_a()
1652
self.create_and_test_symlink(state, entry)
1654
self.create_and_test_file(state, entry)
1656
def test_update_symlink_to_dir(self):
1657
"""Symlink becomes a directory"""
1658
if not has_symlinks():
1659
raise TestSkipped("No symlink support")
1660
state, entry = self.get_state_with_a()
1661
self.create_and_test_symlink(state, entry)
1663
self.create_and_test_dir(state, entry)
1665
def test__is_executable_win32(self):
1666
state, entry = self.get_state_with_a()
1667
self.build_tree(['a'])
1669
# Make sure we are using the win32 implementation of _is_executable
1670
state._is_executable = state._is_executable_win32
1672
# The file on disk is not executable, but we are marking it as though
1673
# it is. With _is_executable_win32 we ignore what is on disk.
1674
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1676
stat_value = os.lstat('a')
1677
packed_stat = dirstate.pack_stat(stat_value)
1679
state.adjust_time(-10) # Make sure everything is new
1680
# Make sure it wants to kkkkkkkk
1681
state.update_entry(entry, abspath='a', stat_value=stat_value)
1683
# The row is updated, but the executable bit stays set.
1684
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1685
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1688
class TestPackStat(TestCaseWithTransport):
1864
1690
def assertPackStat(self, expected, stat_value):
1865
1691
"""Check the packed and serialized form of a stat value."""
2205
2108
state._validate)
2206
2109
self.assertContainsRe(str(e),
2207
2110
'file a-id is absent in row')
2210
class TestDirstateTreeReference(TestCaseWithDirState):
2212
def test_reference_revision_is_none(self):
2213
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2214
subtree = self.make_branch_and_tree('tree/subtree',
2215
format='dirstate-with-subtree')
2216
subtree.set_root_id('subtree')
2217
tree.add_reference(subtree)
2219
state = dirstate.DirState.from_tree(tree, 'dirstate')
2220
key = ('', 'subtree', 'subtree')
2221
expected = ('', [(key,
2222
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2225
self.assertEqual(expected, state._find_block(key))
2230
class TestDiscardMergeParents(TestCaseWithDirState):
2232
def test_discard_no_parents(self):
2233
# This should be a no-op
2234
state = self.create_empty_dirstate()
2235
self.addCleanup(state.unlock)
2236
state._discard_merge_parents()
2239
def test_discard_one_parent(self):
2241
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2242
root_entry_direntry = ('', '', 'a-root-value'), [
2243
('d', '', 0, False, packed_stat),
2244
('d', '', 0, False, packed_stat),
2247
dirblocks.append(('', [root_entry_direntry]))
2248
dirblocks.append(('', []))
2250
state = self.create_empty_dirstate()
2251
self.addCleanup(state.unlock)
2252
state._set_data(['parent-id'], dirblocks[:])
2255
state._discard_merge_parents()
2257
self.assertEqual(dirblocks, state._dirblocks)
2259
def test_discard_simple(self):
2261
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2262
root_entry_direntry = ('', '', 'a-root-value'), [
2263
('d', '', 0, False, packed_stat),
2264
('d', '', 0, False, packed_stat),
2265
('d', '', 0, False, packed_stat),
2267
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2268
('d', '', 0, False, packed_stat),
2269
('d', '', 0, False, packed_stat),
2272
dirblocks.append(('', [root_entry_direntry]))
2273
dirblocks.append(('', []))
2275
state = self.create_empty_dirstate()
2276
self.addCleanup(state.unlock)
2277
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2280
# This should strip of the extra column
2281
state._discard_merge_parents()
2283
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2284
self.assertEqual(expected_dirblocks, state._dirblocks)
2286
def test_discard_absent(self):
2287
"""If entries are only in a merge, discard should remove the entries"""
2288
null_stat = dirstate.DirState.NULLSTAT
2289
present_dir = ('d', '', 0, False, null_stat)
2290
present_file = ('f', '', 0, False, null_stat)
2291
absent = dirstate.DirState.NULL_PARENT_DETAILS
2292
root_key = ('', '', 'a-root-value')
2293
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2294
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2295
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2296
('', [(file_in_merged_key,
2297
[absent, absent, present_file]),
2299
[present_file, present_file, present_file]),
2303
state = self.create_empty_dirstate()
2304
self.addCleanup(state.unlock)
2305
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2308
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2309
('', [(file_in_root_key,
2310
[present_file, present_file]),
2313
state._discard_merge_parents()
2315
self.assertEqual(exp_dirblocks, state._dirblocks)
2317
def test_discard_renamed(self):
2318
null_stat = dirstate.DirState.NULLSTAT
2319
present_dir = ('d', '', 0, False, null_stat)
2320
present_file = ('f', '', 0, False, null_stat)
2321
absent = dirstate.DirState.NULL_PARENT_DETAILS
2322
root_key = ('', '', 'a-root-value')
2323
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2324
# Renamed relative to parent
2325
file_rename_s_key = ('', 'file-s', 'b-file-id')
2326
file_rename_t_key = ('', 'file-t', 'b-file-id')
2327
# And one that is renamed between the parents, but absent in this
2328
key_in_1 = ('', 'file-in-1', 'c-file-id')
2329
key_in_2 = ('', 'file-in-2', 'c-file-id')
2332
('', [(root_key, [present_dir, present_dir, present_dir])]),
2334
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2336
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2338
[present_file, present_file, present_file]),
2340
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2342
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2346
('', [(root_key, [present_dir, present_dir])]),
2347
('', [(key_in_1, [absent, present_file]),
2348
(file_in_root_key, [present_file, present_file]),
2349
(file_rename_t_key, [present_file, absent]),
2352
state = self.create_empty_dirstate()
2353
self.addCleanup(state.unlock)
2354
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2357
state._discard_merge_parents()
2359
self.assertEqual(exp_dirblocks, state._dirblocks)
2361
def test_discard_all_subdir(self):
2362
null_stat = dirstate.DirState.NULLSTAT
2363
present_dir = ('d', '', 0, False, null_stat)
2364
present_file = ('f', '', 0, False, null_stat)
2365
absent = dirstate.DirState.NULL_PARENT_DETAILS
2366
root_key = ('', '', 'a-root-value')
2367
subdir_key = ('', 'sub', 'dir-id')
2368
child1_key = ('sub', 'child1', 'child1-id')
2369
child2_key = ('sub', 'child2', 'child2-id')
2370
child3_key = ('sub', 'child3', 'child3-id')
2373
('', [(root_key, [present_dir, present_dir, present_dir])]),
2374
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2375
('sub', [(child1_key, [absent, absent, present_file]),
2376
(child2_key, [absent, absent, present_file]),
2377
(child3_key, [absent, absent, present_file]),
2381
('', [(root_key, [present_dir, present_dir])]),
2382
('', [(subdir_key, [present_dir, present_dir])]),
2385
state = self.create_empty_dirstate()
2386
self.addCleanup(state.unlock)
2387
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2390
state._discard_merge_parents()
2392
self.assertEqual(exp_dirblocks, state._dirblocks)
2395
class Test_InvEntryToDetails(tests.TestCase):
2397
def assertDetails(self, expected, inv_entry):
2398
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2399
self.assertEqual(expected, details)
2400
# details should always allow join() and always be a plain str when
2402
(minikind, fingerprint, size, executable, tree_data) = details
2403
self.assertIsInstance(minikind, str)
2404
self.assertIsInstance(fingerprint, str)
2405
self.assertIsInstance(tree_data, str)
2407
def test_unicode_symlink(self):
2408
inv_entry = inventory.InventoryLink('link-file-id',
2409
u'nam\N{Euro Sign}e',
2411
inv_entry.revision = 'link-revision-id'
2412
target = u'link-targ\N{Euro Sign}t'
2413
inv_entry.symlink_target = target
2414
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2415
'link-revision-id'), inv_entry)
2418
class TestSHA1Provider(tests.TestCaseInTempDir):
2420
def test_sha1provider_is_an_interface(self):
2421
p = dirstate.SHA1Provider()
2422
self.assertRaises(NotImplementedError, p.sha1, "foo")
2423
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2425
def test_defaultsha1provider_sha1(self):
2426
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2427
self.build_tree_contents([('foo', text)])
2428
expected_sha = osutils.sha_string(text)
2429
p = dirstate.DefaultSHA1Provider()
2430
self.assertEqual(expected_sha, p.sha1('foo'))
2432
def test_defaultsha1provider_stat_and_sha1(self):
2433
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2434
self.build_tree_contents([('foo', text)])
2435
expected_sha = osutils.sha_string(text)
2436
p = dirstate.DefaultSHA1Provider()
2437
statvalue, sha1 = p.stat_and_sha1('foo')
2438
self.assertTrue(len(statvalue) >= 10)
2439
self.assertEqual(len(text), statvalue.st_size)
2440
self.assertEqual(expected_sha, sha1)
2443
class _Repo(object):
2444
"""A minimal api to get InventoryRevisionTree to work."""
2447
default_format = bzrdir.format_registry.make_bzrdir('default')
2448
self._format = default_format.repository_format
2450
def lock_read(self):
2457
class TestUpdateBasisByDelta(tests.TestCase):
2459
def path_to_ie(self, path, file_id, rev_id, dir_ids):
2460
if path.endswith('/'):
2465
dirname, basename = osutils.split(path)
2467
dir_id = dir_ids[dirname]
2469
dir_id = osutils.basename(dirname) + '-id'
2471
ie = inventory.InventoryDirectory(file_id, basename, dir_id)
2472
dir_ids[path] = file_id
2474
ie = inventory.InventoryFile(file_id, basename, dir_id)
2477
ie.revision = rev_id
2480
def create_tree_from_shape(self, rev_id, shape):
2481
dir_ids = {'': 'root-id'}
2482
inv = inventory.Inventory('root-id', rev_id)
2483
for path, file_id in shape:
2485
# Replace the root entry
2486
del inv._byid[inv.root.file_id]
2487
inv.root.file_id = file_id
2488
inv._byid[file_id] = inv.root
2489
dir_ids[''] = file_id
2491
inv.add(self.path_to_ie(path, file_id, rev_id, dir_ids))
2492
return revisiontree.InventoryRevisionTree(_Repo(), inv, rev_id)
2494
def create_empty_dirstate(self):
2495
fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
2496
self.addCleanup(os.remove, path)
2498
state = dirstate.DirState.initialize(path)
2499
self.addCleanup(state.unlock)
2502
def create_inv_delta(self, delta, rev_id):
2503
"""Translate a 'delta shape' into an actual InventoryDelta"""
2504
dir_ids = {'': 'root-id'}
2506
for old_path, new_path, file_id in delta:
2507
if old_path is not None and old_path.endswith('/'):
2508
# Don't have to actually do anything for this, because only
2509
# new_path creates InventoryEntries
2510
old_path = old_path[:-1]
2511
if new_path is None: # Delete
2512
inv_delta.append((old_path, None, file_id, None))
2514
ie = self.path_to_ie(new_path, file_id, rev_id, dir_ids)
2515
inv_delta.append((old_path, new_path, file_id, ie))
2518
def assertUpdate(self, active, basis, target):
2519
"""Assert that update_basis_by_delta works how we want.
2521
Set up a DirState object with active_shape for tree 0, basis_shape for
2522
tree 1. Then apply the delta from basis_shape to target_shape,
2523
and assert that the DirState is still valid, and that its stored
2524
content matches the target_shape.
2526
active_tree = self.create_tree_from_shape('active', active)
2527
basis_tree = self.create_tree_from_shape('basis', basis)
2528
target_tree = self.create_tree_from_shape('target', target)
2529
state = self.create_empty_dirstate()
2530
state.set_state_from_scratch(active_tree.inventory,
2531
[('basis', basis_tree)], [])
2532
delta = target_tree.inventory._make_delta(basis_tree.inventory)
2533
state.update_basis_by_delta(delta, 'target')
2535
dirstate_tree = workingtree_4.DirStateRevisionTree(state,
2537
# The target now that delta has been applied should match the
2539
self.assertEqual([], list(dirstate_tree.iter_changes(target_tree)))
2540
# And the dirblock state should be identical to the state if we created
2542
state2 = self.create_empty_dirstate()
2543
state2.set_state_from_scratch(active_tree.inventory,
2544
[('target', target_tree)], [])
2545
self.assertEqual(state2._dirblocks, state._dirblocks)
2548
def assertBadDelta(self, active, basis, delta):
2549
"""Test that we raise InconsistentDelta when appropriate.
2551
:param active: The active tree shape
2552
:param basis: The basis tree shape
2553
:param delta: A description of the delta to apply. Similar to the form
2554
for regular inventory deltas, but omitting the InventoryEntry.
2555
So adding a file is: (None, 'path', 'file-id')
2556
Adding a directory is: (None, 'path/', 'dir-id')
2557
Renaming a dir is: ('old/', 'new/', 'dir-id')
2560
active_tree = self.create_tree_from_shape('active', active)
2561
basis_tree = self.create_tree_from_shape('basis', basis)
2562
inv_delta = self.create_inv_delta(delta, 'target')
2563
state = self.create_empty_dirstate()
2564
state.set_state_from_scratch(active_tree.inventory,
2565
[('basis', basis_tree)], [])
2566
self.assertRaises(errors.InconsistentDelta,
2567
state.update_basis_by_delta, inv_delta, 'target')
2569
## state.update_basis_by_delta(inv_delta, 'target')
2570
## except errors.InconsistentDelta, e:
2571
## import pdb; pdb.set_trace()
2573
## import pdb; pdb.set_trace()
2574
self.assertTrue(state._changes_aborted)
2576
def test_remove_file_matching_active_state(self):
2577
state = self.assertUpdate(
2579
basis =[('file', 'file-id')],
2583
def test_remove_file_present_in_active_state(self):
2584
state = self.assertUpdate(
2585
active=[('file', 'file-id')],
2586
basis =[('file', 'file-id')],
2590
def test_remove_file_present_elsewhere_in_active_state(self):
2591
state = self.assertUpdate(
2592
active=[('other-file', 'file-id')],
2593
basis =[('file', 'file-id')],
2597
def test_remove_file_active_state_has_diff_file(self):
2598
state = self.assertUpdate(
2599
active=[('file', 'file-id-2')],
2600
basis =[('file', 'file-id')],
2604
def test_remove_file_active_state_has_diff_file_and_file_elsewhere(self):
2605
state = self.assertUpdate(
2606
active=[('file', 'file-id-2'),
2607
('other-file', 'file-id')],
2608
basis =[('file', 'file-id')],
2612
def test_add_file_matching_active_state(self):
2613
state = self.assertUpdate(
2614
active=[('file', 'file-id')],
2616
target=[('file', 'file-id')],
2619
def test_add_file_missing_in_active_state(self):
2620
state = self.assertUpdate(
2623
target=[('file', 'file-id')],
2626
def test_add_file_elsewhere_in_active_state(self):
2627
state = self.assertUpdate(
2628
active=[('other-file', 'file-id')],
2630
target=[('file', 'file-id')],
2633
def test_add_file_active_state_has_diff_file_and_file_elsewhere(self):
2634
state = self.assertUpdate(
2635
active=[('other-file', 'file-id'),
2636
('file', 'file-id-2')],
2638
target=[('file', 'file-id')],
2641
def test_rename_file_matching_active_state(self):
2642
state = self.assertUpdate(
2643
active=[('other-file', 'file-id')],
2644
basis =[('file', 'file-id')],
2645
target=[('other-file', 'file-id')],
2648
def test_rename_file_missing_in_active_state(self):
2649
state = self.assertUpdate(
2651
basis =[('file', 'file-id')],
2652
target=[('other-file', 'file-id')],
2655
def test_rename_file_present_elsewhere_in_active_state(self):
2656
state = self.assertUpdate(
2657
active=[('third', 'file-id')],
2658
basis =[('file', 'file-id')],
2659
target=[('other-file', 'file-id')],
2662
def test_rename_file_active_state_has_diff_source_file(self):
2663
state = self.assertUpdate(
2664
active=[('file', 'file-id-2')],
2665
basis =[('file', 'file-id')],
2666
target=[('other-file', 'file-id')],
2669
def test_rename_file_active_state_has_diff_target_file(self):
2670
state = self.assertUpdate(
2671
active=[('other-file', 'file-id-2')],
2672
basis =[('file', 'file-id')],
2673
target=[('other-file', 'file-id')],
2676
def test_rename_file_active_has_swapped_files(self):
2677
state = self.assertUpdate(
2678
active=[('file', 'file-id'),
2679
('other-file', 'file-id-2')],
2680
basis= [('file', 'file-id'),
2681
('other-file', 'file-id-2')],
2682
target=[('file', 'file-id-2'),
2683
('other-file', 'file-id')])
2685
def test_rename_file_basis_has_swapped_files(self):
2686
state = self.assertUpdate(
2687
active=[('file', 'file-id'),
2688
('other-file', 'file-id-2')],
2689
basis= [('file', 'file-id-2'),
2690
('other-file', 'file-id')],
2691
target=[('file', 'file-id'),
2692
('other-file', 'file-id-2')])
2694
def test_rename_directory_with_contents(self):
2695
state = self.assertUpdate( # active matches basis
2696
active=[('dir1/', 'dir-id'),
2697
('dir1/file', 'file-id')],
2698
basis= [('dir1/', 'dir-id'),
2699
('dir1/file', 'file-id')],
2700
target=[('dir2/', 'dir-id'),
2701
('dir2/file', 'file-id')])
2702
state = self.assertUpdate( # active matches target
2703
active=[('dir2/', 'dir-id'),
2704
('dir2/file', 'file-id')],
2705
basis= [('dir1/', 'dir-id'),
2706
('dir1/file', 'file-id')],
2707
target=[('dir2/', 'dir-id'),
2708
('dir2/file', 'file-id')])
2709
state = self.assertUpdate( # active empty
2711
basis= [('dir1/', 'dir-id'),
2712
('dir1/file', 'file-id')],
2713
target=[('dir2/', 'dir-id'),
2714
('dir2/file', 'file-id')])
2715
state = self.assertUpdate( # active present at other location
2716
active=[('dir3/', 'dir-id'),
2717
('dir3/file', 'file-id')],
2718
basis= [('dir1/', 'dir-id'),
2719
('dir1/file', 'file-id')],
2720
target=[('dir2/', 'dir-id'),
2721
('dir2/file', 'file-id')])
2722
state = self.assertUpdate( # active has different ids
2723
active=[('dir1/', 'dir1-id'),
2724
('dir1/file', 'file1-id'),
2725
('dir2/', 'dir2-id'),
2726
('dir2/file', 'file2-id')],
2727
basis= [('dir1/', 'dir-id'),
2728
('dir1/file', 'file-id')],
2729
target=[('dir2/', 'dir-id'),
2730
('dir2/file', 'file-id')])
2732
def test_invalid_file_not_present(self):
2733
state = self.assertBadDelta(
2734
active=[('file', 'file-id')],
2735
basis= [('file', 'file-id')],
2736
delta=[('other-file', 'file', 'file-id')])
2738
def test_invalid_new_id_same_path(self):
2739
# The bad entry comes after
2740
state = self.assertBadDelta(
2741
active=[('file', 'file-id')],
2742
basis= [('file', 'file-id')],
2743
delta=[(None, 'file', 'file-id-2')])
2744
# The bad entry comes first
2745
state = self.assertBadDelta(
2746
active=[('file', 'file-id-2')],
2747
basis=[('file', 'file-id-2')],
2748
delta=[(None, 'file', 'file-id')])
2750
def test_invalid_existing_id(self):
2751
state = self.assertBadDelta(
2752
active=[('file', 'file-id')],
2753
basis= [('file', 'file-id')],
2754
delta=[(None, 'file', 'file-id')])
2756
def test_invalid_parent_missing(self):
2757
state = self.assertBadDelta(
2760
delta=[(None, 'path/path2', 'file-id')])
2761
# Note: we force the active tree to have the directory, by knowing how
2762
# path_to_ie handles entries with missing parents
2763
state = self.assertBadDelta(
2764
active=[('path/', 'path-id')],
2766
delta=[(None, 'path/path2', 'file-id')])
2767
state = self.assertBadDelta(
2768
active=[('path/', 'path-id'),
2769
('path/path2', 'file-id')],
2771
delta=[(None, 'path/path2', 'file-id')])
2773
def test_renamed_dir_same_path(self):
2774
# We replace the parent directory, with another parent dir. But the C
2775
# file doesn't look like it has been moved.
2776
state = self.assertUpdate(# Same as basis
2777
active=[('dir/', 'A-id'),
2779
basis= [('dir/', 'A-id'),
2781
target=[('dir/', 'C-id'),
2783
state = self.assertUpdate(# Same as target
2784
active=[('dir/', 'C-id'),
2786
basis= [('dir/', 'A-id'),
2788
target=[('dir/', 'C-id'),
2790
state = self.assertUpdate(# empty active
2792
basis= [('dir/', 'A-id'),
2794
target=[('dir/', 'C-id'),
2796
state = self.assertUpdate(# different active
2797
active=[('dir/', 'D-id'),
2799
basis= [('dir/', 'A-id'),
2801
target=[('dir/', 'C-id'),
2804
def test_parent_child_swap(self):
2805
state = self.assertUpdate(# Same as basis
2806
active=[('A/', 'A-id'),
2809
basis= [('A/', 'A-id'),
2812
target=[('A/', 'B-id'),
2815
state = self.assertUpdate(# Same as target
2816
active=[('A/', 'B-id'),
2819
basis= [('A/', 'A-id'),
2822
target=[('A/', 'B-id'),
2825
state = self.assertUpdate(# empty active
2827
basis= [('A/', 'A-id'),
2830
target=[('A/', 'B-id'),
2833
state = self.assertUpdate(# different active
2834
active=[('D/', 'A-id'),
2837
basis= [('A/', 'A-id'),
2840
target=[('A/', 'B-id'),
2844
def test_change_root_id(self):
2845
state = self.assertUpdate( # same as basis
2846
active=[('', 'root-id'),
2847
('file', 'file-id')],
2848
basis= [('', 'root-id'),
2849
('file', 'file-id')],
2850
target=[('', 'target-root-id'),
2851
('file', 'file-id')])
2852
state = self.assertUpdate( # same as target
2853
active=[('', 'target-root-id'),
2854
('file', 'file-id')],
2855
basis= [('', 'root-id'),
2856
('file', 'file-id')],
2857
target=[('', 'target-root-id'),
2858
('file', 'root-id')])
2859
state = self.assertUpdate( # all different
2860
active=[('', 'active-root-id'),
2861
('file', 'file-id')],
2862
basis= [('', 'root-id'),
2863
('file', 'file-id')],
2864
target=[('', 'target-root-id'),
2865
('file', 'root-id')])
2867
def test_change_file_absent_in_active(self):
2868
state = self.assertUpdate(
2870
basis= [('file', 'file-id')],
2871
target=[('file', 'file-id')])
2873
def test_invalid_changed_file(self):
2874
state = self.assertBadDelta( # Not present in basis
2875
active=[('file', 'file-id')],
2877
delta=[('file', 'file', 'file-id')])
2878
state = self.assertBadDelta( # present at another location in basis
2879
active=[('file', 'file-id')],
2880
basis= [('other-file', 'file-id')],
2881
delta=[('file', 'file', 'file-id')])