907
842
state._validate()
909
844
state.set_parent_trees([('parent-revid', rt)], ghosts=[])
910
root_entry = (('', '', 'TREE_ROOT'),
911
[('d', '', 0, False, 'x'*32),
912
('d', '', 0, False, 'parent-revid')])
913
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
914
self.assertEqual(root_entry,
915
state._get_entry(0, fileid_utf8='TREE_ROOT'))
916
self.assertEqual((None, None),
917
state._get_entry(0, fileid_utf8='Asecond-root-id'))
918
state.set_path_id('', 'Asecond-root-id')
845
state.set_path_id('', 'foobarbaz')
919
846
state._validate()
920
847
# now see that it is what we expected
921
old_root_entry = (('', '', 'TREE_ROOT'),
922
[('a', '', 0, False, ''),
923
('d', '', 0, False, 'parent-revid')])
924
new_root_entry = (('', '', 'Asecond-root-id'),
925
[('d', '', 0, False, ''),
926
('a', '', 0, False, '')])
927
expected_rows = [new_root_entry, old_root_entry]
849
(('', '', 'TREE_ROOT'),
850
[('a', '', 0, False, ''),
851
('d', '', 0, False, 'parent-revid'),
853
(('', '', 'foobarbaz'),
854
[('d', '', 0, False, ''),
855
('a', '', 0, False, ''),
928
858
state._validate()
929
859
self.assertEqual(expected_rows, list(state._iter_entries()))
930
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
931
self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
932
self.assertEqual((None, None),
933
state._get_entry(0, fileid_utf8='TREE_ROOT'))
934
self.assertEqual(old_root_entry,
935
state._get_entry(1, fileid_utf8='TREE_ROOT'))
936
self.assertEqual(new_root_entry,
937
state._get_entry(0, fileid_utf8='Asecond-root-id'))
938
self.assertEqual((None, None),
939
state._get_entry(1, fileid_utf8='Asecond-root-id'))
940
860
# should work across save too
1735
1625
self.st_ino = ino
1736
1626
self.st_mode = mode
1740
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1741
st.st_ino, st.st_mode)
1744
class TestPackStat(tests.TestCaseWithTransport):
1629
class TestUpdateEntry(TestCaseWithDirState):
1630
"""Test the DirState.update_entry functions"""
1632
def get_state_with_a(self):
1633
"""Create a DirState tracking a single object named 'a'"""
1634
state = InstrumentedDirState.initialize('dirstate')
1635
self.addCleanup(state.unlock)
1636
state.add('a', 'a-id', 'file', None, '')
1637
entry = state._get_entry(0, path_utf8='a')
1640
def test_update_entry(self):
1641
state, entry = self.get_state_with_a()
1642
self.build_tree(['a'])
1643
# Add one where we don't provide the stat or sha already
1644
self.assertEqual(('', 'a', 'a-id'), entry[0])
1645
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1647
# Flush the buffers to disk
1649
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1650
state._dirblock_state)
1652
stat_value = os.lstat('a')
1653
packed_stat = dirstate.pack_stat(stat_value)
1654
link_or_sha1 = state.update_entry(entry, abspath='a',
1655
stat_value=stat_value)
1656
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1659
# The dirblock entry should not cache the file's sha1
1660
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1662
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1663
state._dirblock_state)
1664
mode = stat_value.st_mode
1665
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1668
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1669
state._dirblock_state)
1671
# If we do it again right away, we don't know if the file has changed
1672
# so we will re-read the file. Roll the clock back so the file is
1673
# guaranteed to look too new.
1674
state.adjust_time(-10)
1676
link_or_sha1 = state.update_entry(entry, abspath='a',
1677
stat_value=stat_value)
1678
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1679
('sha1', 'a'), ('is_exec', mode, False),
1681
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1683
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1684
state._dirblock_state)
1685
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1689
# However, if we move the clock forward so the file is considered
1690
# "stable", it should just cache the value.
1691
state.adjust_time(+20)
1692
link_or_sha1 = state.update_entry(entry, abspath='a',
1693
stat_value=stat_value)
1694
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1696
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1697
('sha1', 'a'), ('is_exec', mode, False),
1698
('sha1', 'a'), ('is_exec', mode, False),
1700
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1703
# Subsequent calls will just return the cached value
1704
link_or_sha1 = state.update_entry(entry, abspath='a',
1705
stat_value=stat_value)
1706
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1708
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1709
('sha1', 'a'), ('is_exec', mode, False),
1710
('sha1', 'a'), ('is_exec', mode, False),
1712
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1715
def test_update_entry_symlink(self):
1716
"""Update entry should read symlinks."""
1717
if not osutils.has_symlinks():
1718
# PlatformDeficiency / TestSkipped
1719
raise TestSkipped("No symlink support")
1720
state, entry = self.get_state_with_a()
1722
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1723
state._dirblock_state)
1724
os.symlink('target', 'a')
1726
state.adjust_time(-10) # Make the symlink look new
1727
stat_value = os.lstat('a')
1728
packed_stat = dirstate.pack_stat(stat_value)
1729
link_or_sha1 = state.update_entry(entry, abspath='a',
1730
stat_value=stat_value)
1731
self.assertEqual('target', link_or_sha1)
1732
self.assertEqual([('read_link', 'a', '')], state._log)
1733
# Dirblock is not updated (the link is too new)
1734
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1736
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1737
state._dirblock_state)
1739
# Because the stat_value looks new, we should re-read the target
1740
link_or_sha1 = state.update_entry(entry, abspath='a',
1741
stat_value=stat_value)
1742
self.assertEqual('target', link_or_sha1)
1743
self.assertEqual([('read_link', 'a', ''),
1744
('read_link', 'a', ''),
1746
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1748
state.adjust_time(+20) # Skip into the future, all files look old
1749
link_or_sha1 = state.update_entry(entry, abspath='a',
1750
stat_value=stat_value)
1751
self.assertEqual('target', link_or_sha1)
1752
# We need to re-read the link because only now can we cache it
1753
self.assertEqual([('read_link', 'a', ''),
1754
('read_link', 'a', ''),
1755
('read_link', 'a', ''),
1757
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1760
# Another call won't re-read the link
1761
self.assertEqual([('read_link', 'a', ''),
1762
('read_link', 'a', ''),
1763
('read_link', 'a', ''),
1765
link_or_sha1 = state.update_entry(entry, abspath='a',
1766
stat_value=stat_value)
1767
self.assertEqual('target', link_or_sha1)
1768
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1771
def do_update_entry(self, state, entry, abspath):
1772
stat_value = os.lstat(abspath)
1773
return state.update_entry(entry, abspath, stat_value)
1775
def test_update_entry_dir(self):
1776
state, entry = self.get_state_with_a()
1777
self.build_tree(['a/'])
1778
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1780
def test_update_entry_dir_unchanged(self):
1781
state, entry = self.get_state_with_a()
1782
self.build_tree(['a/'])
1783
state.adjust_time(+20)
1784
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1785
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1786
state._dirblock_state)
1788
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1789
state._dirblock_state)
1790
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1791
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1792
state._dirblock_state)
1794
def test_update_entry_file_unchanged(self):
1795
state, entry = self.get_state_with_a()
1796
self.build_tree(['a'])
1797
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1798
state.adjust_time(+20)
1799
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1800
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1801
state._dirblock_state)
1803
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1804
state._dirblock_state)
1805
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1806
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1807
state._dirblock_state)
1809
def create_and_test_file(self, state, entry):
1810
"""Create a file at 'a' and verify the state finds it.
1812
The state should already be versioning *something* at 'a'. This makes
1813
sure that state.update_entry recognizes it as a file.
1815
self.build_tree(['a'])
1816
stat_value = os.lstat('a')
1817
packed_stat = dirstate.pack_stat(stat_value)
1819
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1820
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1822
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1826
def create_and_test_dir(self, state, entry):
1827
"""Create a directory at 'a' and verify the state finds it.
1829
The state should already be versioning *something* at 'a'. This makes
1830
sure that state.update_entry recognizes it as a directory.
1832
self.build_tree(['a/'])
1833
stat_value = os.lstat('a')
1834
packed_stat = dirstate.pack_stat(stat_value)
1836
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1837
self.assertIs(None, link_or_sha1)
1838
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1842
def create_and_test_symlink(self, state, entry):
1843
"""Create a symlink at 'a' and verify the state finds it.
1845
The state should already be versioning *something* at 'a'. This makes
1846
sure that state.update_entry recognizes it as a symlink.
1848
This should not be called if this platform does not have symlink
1851
# caller should care about skipping test on platforms without symlinks
1852
os.symlink('path/to/foo', 'a')
1854
stat_value = os.lstat('a')
1855
packed_stat = dirstate.pack_stat(stat_value)
1857
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1858
self.assertEqual('path/to/foo', link_or_sha1)
1859
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1863
def test_update_file_to_dir(self):
1864
"""If a file changes to a directory we return None for the sha.
1865
We also update the inventory record.
1867
state, entry = self.get_state_with_a()
1868
# The file sha1 won't be cached unless the file is old
1869
state.adjust_time(+10)
1870
self.create_and_test_file(state, entry)
1872
self.create_and_test_dir(state, entry)
1874
def test_update_file_to_symlink(self):
1875
"""File becomes a symlink"""
1876
if not osutils.has_symlinks():
1877
# PlatformDeficiency / TestSkipped
1878
raise TestSkipped("No symlink support")
1879
state, entry = self.get_state_with_a()
1880
# The file sha1 won't be cached unless the file is old
1881
state.adjust_time(+10)
1882
self.create_and_test_file(state, entry)
1884
self.create_and_test_symlink(state, entry)
1886
def test_update_dir_to_file(self):
1887
"""Directory becoming a file updates the entry."""
1888
state, entry = self.get_state_with_a()
1889
# The file sha1 won't be cached unless the file is old
1890
state.adjust_time(+10)
1891
self.create_and_test_dir(state, entry)
1893
self.create_and_test_file(state, entry)
1895
def test_update_dir_to_symlink(self):
1896
"""Directory becomes a symlink"""
1897
if not osutils.has_symlinks():
1898
# PlatformDeficiency / TestSkipped
1899
raise TestSkipped("No symlink support")
1900
state, entry = self.get_state_with_a()
1901
# The symlink target won't be cached if it isn't old
1902
state.adjust_time(+10)
1903
self.create_and_test_dir(state, entry)
1905
self.create_and_test_symlink(state, entry)
1907
def test_update_symlink_to_file(self):
1908
"""Symlink becomes a file"""
1909
if not has_symlinks():
1910
raise TestSkipped("No symlink support")
1911
state, entry = self.get_state_with_a()
1912
# The symlink and file info won't be cached unless old
1913
state.adjust_time(+10)
1914
self.create_and_test_symlink(state, entry)
1916
self.create_and_test_file(state, entry)
1918
def test_update_symlink_to_dir(self):
1919
"""Symlink becomes a directory"""
1920
if not has_symlinks():
1921
raise TestSkipped("No symlink support")
1922
state, entry = self.get_state_with_a()
1923
# The symlink target won't be cached if it isn't old
1924
state.adjust_time(+10)
1925
self.create_and_test_symlink(state, entry)
1927
self.create_and_test_dir(state, entry)
1929
def test__is_executable_win32(self):
1930
state, entry = self.get_state_with_a()
1931
self.build_tree(['a'])
1933
# Make sure we are using the win32 implementation of _is_executable
1934
state._is_executable = state._is_executable_win32
1936
# The file on disk is not executable, but we are marking it as though
1937
# it is. With _is_executable_win32 we ignore what is on disk.
1938
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1940
stat_value = os.lstat('a')
1941
packed_stat = dirstate.pack_stat(stat_value)
1943
state.adjust_time(-10) # Make sure everything is new
1944
state.update_entry(entry, abspath='a', stat_value=stat_value)
1946
# The row is updated, but the executable bit stays set.
1947
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1950
# Make the disk object look old enough to cache
1951
state.adjust_time(+20)
1952
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1953
state.update_entry(entry, abspath='a', stat_value=stat_value)
1954
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1957
class TestPackStat(TestCaseWithTransport):
1746
1959
def assertPackStat(self, expected, stat_value):
1747
1960
"""Check the packed and serialized form of a stat value."""
2107
2321
self.assertEqual(expected, state._find_block(key))
2112
class TestDiscardMergeParents(TestCaseWithDirState):
2114
def test_discard_no_parents(self):
2115
# This should be a no-op
2116
state = self.create_empty_dirstate()
2117
self.addCleanup(state.unlock)
2118
state._discard_merge_parents()
2121
def test_discard_one_parent(self):
2123
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2124
root_entry_direntry = ('', '', 'a-root-value'), [
2125
('d', '', 0, False, packed_stat),
2126
('d', '', 0, False, packed_stat),
2129
dirblocks.append(('', [root_entry_direntry]))
2130
dirblocks.append(('', []))
2132
state = self.create_empty_dirstate()
2133
self.addCleanup(state.unlock)
2134
state._set_data(['parent-id'], dirblocks[:])
2137
state._discard_merge_parents()
2139
self.assertEqual(dirblocks, state._dirblocks)
2141
def test_discard_simple(self):
2143
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2144
root_entry_direntry = ('', '', 'a-root-value'), [
2145
('d', '', 0, False, packed_stat),
2146
('d', '', 0, False, packed_stat),
2147
('d', '', 0, False, packed_stat),
2149
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2150
('d', '', 0, False, packed_stat),
2151
('d', '', 0, False, packed_stat),
2154
dirblocks.append(('', [root_entry_direntry]))
2155
dirblocks.append(('', []))
2157
state = self.create_empty_dirstate()
2158
self.addCleanup(state.unlock)
2159
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2162
# This should strip of the extra column
2163
state._discard_merge_parents()
2165
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2166
self.assertEqual(expected_dirblocks, state._dirblocks)
2168
def test_discard_absent(self):
2169
"""If entries are only in a merge, discard should remove the entries"""
2170
null_stat = dirstate.DirState.NULLSTAT
2171
present_dir = ('d', '', 0, False, null_stat)
2172
present_file = ('f', '', 0, False, null_stat)
2173
absent = dirstate.DirState.NULL_PARENT_DETAILS
2174
root_key = ('', '', 'a-root-value')
2175
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2176
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2177
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2178
('', [(file_in_merged_key,
2179
[absent, absent, present_file]),
2181
[present_file, present_file, present_file]),
2185
state = self.create_empty_dirstate()
2186
self.addCleanup(state.unlock)
2187
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2190
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2191
('', [(file_in_root_key,
2192
[present_file, present_file]),
2195
state._discard_merge_parents()
2197
self.assertEqual(exp_dirblocks, state._dirblocks)
2199
def test_discard_renamed(self):
2200
null_stat = dirstate.DirState.NULLSTAT
2201
present_dir = ('d', '', 0, False, null_stat)
2202
present_file = ('f', '', 0, False, null_stat)
2203
absent = dirstate.DirState.NULL_PARENT_DETAILS
2204
root_key = ('', '', 'a-root-value')
2205
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2206
# Renamed relative to parent
2207
file_rename_s_key = ('', 'file-s', 'b-file-id')
2208
file_rename_t_key = ('', 'file-t', 'b-file-id')
2209
# And one that is renamed between the parents, but absent in this
2210
key_in_1 = ('', 'file-in-1', 'c-file-id')
2211
key_in_2 = ('', 'file-in-2', 'c-file-id')
2214
('', [(root_key, [present_dir, present_dir, present_dir])]),
2216
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2218
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2220
[present_file, present_file, present_file]),
2222
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2224
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2228
('', [(root_key, [present_dir, present_dir])]),
2229
('', [(key_in_1, [absent, present_file]),
2230
(file_in_root_key, [present_file, present_file]),
2231
(file_rename_t_key, [present_file, absent]),
2234
state = self.create_empty_dirstate()
2235
self.addCleanup(state.unlock)
2236
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2239
state._discard_merge_parents()
2241
self.assertEqual(exp_dirblocks, state._dirblocks)
2243
def test_discard_all_subdir(self):
2244
null_stat = dirstate.DirState.NULLSTAT
2245
present_dir = ('d', '', 0, False, null_stat)
2246
present_file = ('f', '', 0, False, null_stat)
2247
absent = dirstate.DirState.NULL_PARENT_DETAILS
2248
root_key = ('', '', 'a-root-value')
2249
subdir_key = ('', 'sub', 'dir-id')
2250
child1_key = ('sub', 'child1', 'child1-id')
2251
child2_key = ('sub', 'child2', 'child2-id')
2252
child3_key = ('sub', 'child3', 'child3-id')
2255
('', [(root_key, [present_dir, present_dir, present_dir])]),
2256
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2257
('sub', [(child1_key, [absent, absent, present_file]),
2258
(child2_key, [absent, absent, present_file]),
2259
(child3_key, [absent, absent, present_file]),
2263
('', [(root_key, [present_dir, present_dir])]),
2264
('', [(subdir_key, [present_dir, present_dir])]),
2267
state = self.create_empty_dirstate()
2268
self.addCleanup(state.unlock)
2269
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2272
state._discard_merge_parents()
2274
self.assertEqual(exp_dirblocks, state._dirblocks)
2277
class Test_InvEntryToDetails(tests.TestCase):
2279
def assertDetails(self, expected, inv_entry):
2280
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2281
self.assertEqual(expected, details)
2282
# details should always allow join() and always be a plain str when
2284
(minikind, fingerprint, size, executable, tree_data) = details
2285
self.assertIsInstance(minikind, str)
2286
self.assertIsInstance(fingerprint, str)
2287
self.assertIsInstance(tree_data, str)
2289
def test_unicode_symlink(self):
2290
inv_entry = inventory.InventoryLink('link-file-id',
2291
u'nam\N{Euro Sign}e',
2293
inv_entry.revision = 'link-revision-id'
2294
target = u'link-targ\N{Euro Sign}t'
2295
inv_entry.symlink_target = target
2296
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2297
'link-revision-id'), inv_entry)
2300
class TestSHA1Provider(tests.TestCaseInTempDir):
2302
def test_sha1provider_is_an_interface(self):
2303
p = dirstate.SHA1Provider()
2304
self.assertRaises(NotImplementedError, p.sha1, "foo")
2305
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2307
def test_defaultsha1provider_sha1(self):
2308
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2309
self.build_tree_contents([('foo', text)])
2310
expected_sha = osutils.sha_string(text)
2311
p = dirstate.DefaultSHA1Provider()
2312
self.assertEqual(expected_sha, p.sha1('foo'))
2314
def test_defaultsha1provider_stat_and_sha1(self):
2315
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2316
self.build_tree_contents([('foo', text)])
2317
expected_sha = osutils.sha_string(text)
2318
p = dirstate.DefaultSHA1Provider()
2319
statvalue, sha1 = p.stat_and_sha1('foo')
2320
self.assertTrue(len(statvalue) >= 10)
2321
self.assertEqual(len(text), statvalue.st_size)
2322
self.assertEqual(expected_sha, sha1)