1620
class TestIterChildEntries(TestCaseWithDirState):
1622
def create_dirstate_with_two_trees(self):
1623
"""This dirstate contains multiple files and directories.
1633
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1635
Notice that a/e is an empty directory.
1637
There is one parent tree, which has the same shape with the following variations:
1638
b/g in the parent is gone.
1639
b/h in the parent has a different id
1640
b/i is new in the parent
1641
c is renamed to b/j in the parent
1643
:return: The dirstate, still write-locked.
1645
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1646
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1647
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1648
root_entry = ('', '', 'a-root-value'), [
1649
('d', '', 0, False, packed_stat),
1650
('d', '', 0, False, 'parent-revid'),
1652
a_entry = ('', 'a', 'a-dir'), [
1653
('d', '', 0, False, packed_stat),
1654
('d', '', 0, False, 'parent-revid'),
1656
b_entry = ('', 'b', 'b-dir'), [
1657
('d', '', 0, False, packed_stat),
1658
('d', '', 0, False, 'parent-revid'),
1660
c_entry = ('', 'c', 'c-file'), [
1661
('f', null_sha, 10, False, packed_stat),
1662
('r', 'b/j', 0, False, ''),
1664
d_entry = ('', 'd', 'd-file'), [
1665
('f', null_sha, 20, False, packed_stat),
1666
('f', 'd', 20, False, 'parent-revid'),
1668
e_entry = ('a', 'e', 'e-dir'), [
1669
('d', '', 0, False, packed_stat),
1670
('d', '', 0, False, 'parent-revid'),
1672
f_entry = ('a', 'f', 'f-file'), [
1673
('f', null_sha, 30, False, packed_stat),
1674
('f', 'f', 20, False, 'parent-revid'),
1676
g_entry = ('b', 'g', 'g-file'), [
1677
('f', null_sha, 30, False, packed_stat),
1678
NULL_PARENT_DETAILS,
1680
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1681
('f', null_sha, 40, False, packed_stat),
1682
NULL_PARENT_DETAILS,
1684
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1685
NULL_PARENT_DETAILS,
1686
('f', 'h', 20, False, 'parent-revid'),
1688
i_entry = ('b', 'i', 'i-file'), [
1689
NULL_PARENT_DETAILS,
1690
('f', 'h', 20, False, 'parent-revid'),
1692
j_entry = ('b', 'j', 'c-file'), [
1693
('r', 'c', 0, False, ''),
1694
('f', 'j', 20, False, 'parent-revid'),
1697
dirblocks.append(('', [root_entry]))
1698
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1699
dirblocks.append(('a', [e_entry, f_entry]))
1700
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1701
state = dirstate.DirState.initialize('dirstate')
1704
state._set_data(['parent'], dirblocks)
1708
return state, dirblocks
1710
def test_iter_children_b(self):
1711
state, dirblocks = self.create_dirstate_with_two_trees()
1712
self.addCleanup(state.unlock)
1713
expected_result = []
1714
expected_result.append(dirblocks[3][1][2]) # h2
1715
expected_result.append(dirblocks[3][1][3]) # i
1716
expected_result.append(dirblocks[3][1][4]) # j
1717
self.assertEqual(expected_result,
1718
list(state._iter_child_entries(1, 'b')))
1720
def test_iter_child_root(self):
1721
state, dirblocks = self.create_dirstate_with_two_trees()
1722
self.addCleanup(state.unlock)
1723
expected_result = []
1724
expected_result.append(dirblocks[1][1][0]) # a
1725
expected_result.append(dirblocks[1][1][1]) # b
1726
expected_result.append(dirblocks[1][1][3]) # d
1727
expected_result.append(dirblocks[2][1][0]) # e
1728
expected_result.append(dirblocks[2][1][1]) # f
1729
expected_result.append(dirblocks[3][1][2]) # h2
1730
expected_result.append(dirblocks[3][1][3]) # i
1731
expected_result.append(dirblocks[3][1][4]) # j
1732
self.assertEqual(expected_result,
1733
list(state._iter_child_entries(1, '')))
1736
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1333
class TestDirstateSortOrder(TestCaseWithTransport):
1737
1334
"""Test that DirState adds entries in the right order."""
1739
1336
def test_add_sorting(self):
1850
1443
self.st_ino = ino
1851
1444
self.st_mode = mode
1855
return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
1856
st.st_ino, st.st_mode)
1859
class TestPackStat(tests.TestCaseWithTransport):
1447
class TestUpdateEntry(TestCaseWithDirState):
1448
"""Test the DirState.update_entry functions"""
1450
def get_state_with_a(self):
1451
"""Create a DirState tracking a single object named 'a'"""
1452
state = InstrumentedDirState.initialize('dirstate')
1453
self.addCleanup(state.unlock)
1454
state.add('a', 'a-id', 'file', None, '')
1455
entry = state._get_entry(0, path_utf8='a')
1458
def test_update_entry(self):
1459
state, entry = self.get_state_with_a()
1460
self.build_tree(['a'])
1461
# Add one where we don't provide the stat or sha already
1462
self.assertEqual(('', 'a', 'a-id'), entry[0])
1463
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1465
# Flush the buffers to disk
1467
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1468
state._dirblock_state)
1470
stat_value = os.lstat('a')
1471
packed_stat = dirstate.pack_stat(stat_value)
1472
link_or_sha1 = state.update_entry(entry, abspath='a',
1473
stat_value=stat_value)
1474
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1477
# The dirblock entry should not cache the file's sha1
1478
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1480
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1481
state._dirblock_state)
1482
mode = stat_value.st_mode
1483
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1486
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1487
state._dirblock_state)
1489
# If we do it again right away, we don't know if the file has changed
1490
# so we will re-read the file. Roll the clock back so the file is
1491
# guaranteed to look too new.
1492
state.adjust_time(-10)
1494
link_or_sha1 = state.update_entry(entry, abspath='a',
1495
stat_value=stat_value)
1496
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1497
('sha1', 'a'), ('is_exec', mode, False),
1499
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1501
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1502
state._dirblock_state)
1503
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1507
# However, if we move the clock forward so the file is considered
1508
# "stable", it should just cache the value.
1509
state.adjust_time(+20)
1510
link_or_sha1 = state.update_entry(entry, abspath='a',
1511
stat_value=stat_value)
1512
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1514
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1515
('sha1', 'a'), ('is_exec', mode, False),
1516
('sha1', 'a'), ('is_exec', mode, False),
1518
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1521
# Subsequent calls will just return the cached value
1522
link_or_sha1 = state.update_entry(entry, abspath='a',
1523
stat_value=stat_value)
1524
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1526
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1527
('sha1', 'a'), ('is_exec', mode, False),
1528
('sha1', 'a'), ('is_exec', mode, False),
1530
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1533
def test_update_entry_symlink(self):
1534
"""Update entry should read symlinks."""
1535
if not osutils.has_symlinks():
1536
# PlatformDeficiency / TestSkipped
1537
raise TestSkipped("No symlink support")
1538
state, entry = self.get_state_with_a()
1540
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1541
state._dirblock_state)
1542
os.symlink('target', 'a')
1544
state.adjust_time(-10) # Make the symlink look new
1545
stat_value = os.lstat('a')
1546
packed_stat = dirstate.pack_stat(stat_value)
1547
link_or_sha1 = state.update_entry(entry, abspath='a',
1548
stat_value=stat_value)
1549
self.assertEqual('target', link_or_sha1)
1550
self.assertEqual([('read_link', 'a', '')], state._log)
1551
# Dirblock is not updated (the link is too new)
1552
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1554
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1555
state._dirblock_state)
1557
# Because the stat_value looks new, we should re-read the target
1558
link_or_sha1 = state.update_entry(entry, abspath='a',
1559
stat_value=stat_value)
1560
self.assertEqual('target', link_or_sha1)
1561
self.assertEqual([('read_link', 'a', ''),
1562
('read_link', 'a', ''),
1564
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1566
state.adjust_time(+20) # Skip into the future, all files look old
1567
link_or_sha1 = state.update_entry(entry, abspath='a',
1568
stat_value=stat_value)
1569
self.assertEqual('target', link_or_sha1)
1570
# We need to re-read the link because only now can we cache it
1571
self.assertEqual([('read_link', 'a', ''),
1572
('read_link', 'a', ''),
1573
('read_link', 'a', ''),
1575
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1578
# Another call won't re-read the link
1579
self.assertEqual([('read_link', 'a', ''),
1580
('read_link', 'a', ''),
1581
('read_link', 'a', ''),
1583
link_or_sha1 = state.update_entry(entry, abspath='a',
1584
stat_value=stat_value)
1585
self.assertEqual('target', link_or_sha1)
1586
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1589
def do_update_entry(self, state, entry, abspath):
1590
stat_value = os.lstat(abspath)
1591
return state.update_entry(entry, abspath, stat_value)
1593
def test_update_entry_dir(self):
1594
state, entry = self.get_state_with_a()
1595
self.build_tree(['a/'])
1596
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1598
def test_update_entry_dir_unchanged(self):
1599
state, entry = self.get_state_with_a()
1600
self.build_tree(['a/'])
1601
state.adjust_time(+20)
1602
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1603
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1604
state._dirblock_state)
1606
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1607
state._dirblock_state)
1608
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1609
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1610
state._dirblock_state)
1612
def test_update_entry_file_unchanged(self):
1613
state, entry = self.get_state_with_a()
1614
self.build_tree(['a'])
1615
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1616
state.adjust_time(+20)
1617
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1618
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1619
state._dirblock_state)
1621
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1622
state._dirblock_state)
1623
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1624
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1625
state._dirblock_state)
1627
def create_and_test_file(self, state, entry):
1628
"""Create a file at 'a' and verify the state finds it.
1630
The state should already be versioning *something* at 'a'. This makes
1631
sure that state.update_entry recognizes it as a file.
1633
self.build_tree(['a'])
1634
stat_value = os.lstat('a')
1635
packed_stat = dirstate.pack_stat(stat_value)
1637
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1638
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1640
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1644
def create_and_test_dir(self, state, entry):
1645
"""Create a directory at 'a' and verify the state finds it.
1647
The state should already be versioning *something* at 'a'. This makes
1648
sure that state.update_entry recognizes it as a directory.
1650
self.build_tree(['a/'])
1651
stat_value = os.lstat('a')
1652
packed_stat = dirstate.pack_stat(stat_value)
1654
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1655
self.assertIs(None, link_or_sha1)
1656
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1660
def create_and_test_symlink(self, state, entry):
1661
"""Create a symlink at 'a' and verify the state finds it.
1663
The state should already be versioning *something* at 'a'. This makes
1664
sure that state.update_entry recognizes it as a symlink.
1666
This should not be called if this platform does not have symlink
1669
# caller should care about skipping test on platforms without symlinks
1670
os.symlink('path/to/foo', 'a')
1672
stat_value = os.lstat('a')
1673
packed_stat = dirstate.pack_stat(stat_value)
1675
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1676
self.assertEqual('path/to/foo', link_or_sha1)
1677
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1681
def test_update_file_to_dir(self):
1682
"""If a file changes to a directory we return None for the sha.
1683
We also update the inventory record.
1685
state, entry = self.get_state_with_a()
1686
# The file sha1 won't be cached unless the file is old
1687
state.adjust_time(+10)
1688
self.create_and_test_file(state, entry)
1690
self.create_and_test_dir(state, entry)
1692
def test_update_file_to_symlink(self):
1693
"""File becomes a symlink"""
1694
if not osutils.has_symlinks():
1695
# PlatformDeficiency / TestSkipped
1696
raise TestSkipped("No symlink support")
1697
state, entry = self.get_state_with_a()
1698
# The file sha1 won't be cached unless the file is old
1699
state.adjust_time(+10)
1700
self.create_and_test_file(state, entry)
1702
self.create_and_test_symlink(state, entry)
1704
def test_update_dir_to_file(self):
1705
"""Directory becoming a file updates the entry."""
1706
state, entry = self.get_state_with_a()
1707
# The file sha1 won't be cached unless the file is old
1708
state.adjust_time(+10)
1709
self.create_and_test_dir(state, entry)
1711
self.create_and_test_file(state, entry)
1713
def test_update_dir_to_symlink(self):
1714
"""Directory becomes a symlink"""
1715
if not osutils.has_symlinks():
1716
# PlatformDeficiency / TestSkipped
1717
raise TestSkipped("No symlink support")
1718
state, entry = self.get_state_with_a()
1719
# The symlink target won't be cached if it isn't old
1720
state.adjust_time(+10)
1721
self.create_and_test_dir(state, entry)
1723
self.create_and_test_symlink(state, entry)
1725
def test_update_symlink_to_file(self):
1726
"""Symlink becomes a file"""
1727
if not has_symlinks():
1728
raise TestSkipped("No symlink support")
1729
state, entry = self.get_state_with_a()
1730
# The symlink and file info won't be cached unless old
1731
state.adjust_time(+10)
1732
self.create_and_test_symlink(state, entry)
1734
self.create_and_test_file(state, entry)
1736
def test_update_symlink_to_dir(self):
1737
"""Symlink becomes a directory"""
1738
if not has_symlinks():
1739
raise TestSkipped("No symlink support")
1740
state, entry = self.get_state_with_a()
1741
# The symlink target won't be cached if it isn't old
1742
state.adjust_time(+10)
1743
self.create_and_test_symlink(state, entry)
1745
self.create_and_test_dir(state, entry)
1747
def test__is_executable_win32(self):
1748
state, entry = self.get_state_with_a()
1749
self.build_tree(['a'])
1751
# Make sure we are using the win32 implementation of _is_executable
1752
state._is_executable = state._is_executable_win32
1754
# The file on disk is not executable, but we are marking it as though
1755
# it is. With _is_executable_win32 we ignore what is on disk.
1756
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1758
stat_value = os.lstat('a')
1759
packed_stat = dirstate.pack_stat(stat_value)
1761
state.adjust_time(-10) # Make sure everything is new
1762
state.update_entry(entry, abspath='a', stat_value=stat_value)
1764
# The row is updated, but the executable bit stays set.
1765
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1768
# Make the disk object look old enough to cache
1769
state.adjust_time(+20)
1770
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1771
state.update_entry(entry, abspath='a', stat_value=stat_value)
1772
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1775
class TestPackStat(TestCaseWithTransport):
1861
1777
def assertPackStat(self, expected, stat_value):
1862
1778
"""Check the packed and serialized form of a stat value."""
2203
2120
self.assertContainsRe(str(e),
2204
2121
'file a-id is absent in row')
2207
class TestDirstateTreeReference(TestCaseWithDirState):
2209
def test_reference_revision_is_none(self):
2210
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2211
subtree = self.make_branch_and_tree('tree/subtree',
2212
format='dirstate-with-subtree')
2213
subtree.set_root_id('subtree')
2214
tree.add_reference(subtree)
2216
state = dirstate.DirState.from_tree(tree, 'dirstate')
2217
key = ('', 'subtree', 'subtree')
2218
expected = ('', [(key,
2219
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2222
self.assertEqual(expected, state._find_block(key))
2227
class TestDiscardMergeParents(TestCaseWithDirState):
2229
def test_discard_no_parents(self):
2230
# This should be a no-op
2231
state = self.create_empty_dirstate()
2232
self.addCleanup(state.unlock)
2233
state._discard_merge_parents()
2236
def test_discard_one_parent(self):
2238
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2239
root_entry_direntry = ('', '', 'a-root-value'), [
2240
('d', '', 0, False, packed_stat),
2241
('d', '', 0, False, packed_stat),
2244
dirblocks.append(('', [root_entry_direntry]))
2245
dirblocks.append(('', []))
2247
state = self.create_empty_dirstate()
2248
self.addCleanup(state.unlock)
2249
state._set_data(['parent-id'], dirblocks[:])
2252
state._discard_merge_parents()
2254
self.assertEqual(dirblocks, state._dirblocks)
2256
def test_discard_simple(self):
2258
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
2259
root_entry_direntry = ('', '', 'a-root-value'), [
2260
('d', '', 0, False, packed_stat),
2261
('d', '', 0, False, packed_stat),
2262
('d', '', 0, False, packed_stat),
2264
expected_root_entry_direntry = ('', '', 'a-root-value'), [
2265
('d', '', 0, False, packed_stat),
2266
('d', '', 0, False, packed_stat),
2269
dirblocks.append(('', [root_entry_direntry]))
2270
dirblocks.append(('', []))
2272
state = self.create_empty_dirstate()
2273
self.addCleanup(state.unlock)
2274
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2277
# This should strip of the extra column
2278
state._discard_merge_parents()
2280
expected_dirblocks = [('', [expected_root_entry_direntry]), ('', [])]
2281
self.assertEqual(expected_dirblocks, state._dirblocks)
2283
def test_discard_absent(self):
2284
"""If entries are only in a merge, discard should remove the entries"""
2285
null_stat = dirstate.DirState.NULLSTAT
2286
present_dir = ('d', '', 0, False, null_stat)
2287
present_file = ('f', '', 0, False, null_stat)
2288
absent = dirstate.DirState.NULL_PARENT_DETAILS
2289
root_key = ('', '', 'a-root-value')
2290
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2291
file_in_merged_key = ('', 'file-in-merged', 'b-file-id')
2292
dirblocks = [('', [(root_key, [present_dir, present_dir, present_dir])]),
2293
('', [(file_in_merged_key,
2294
[absent, absent, present_file]),
2296
[present_file, present_file, present_file]),
2300
state = self.create_empty_dirstate()
2301
self.addCleanup(state.unlock)
2302
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2305
exp_dirblocks = [('', [(root_key, [present_dir, present_dir])]),
2306
('', [(file_in_root_key,
2307
[present_file, present_file]),
2310
state._discard_merge_parents()
2312
self.assertEqual(exp_dirblocks, state._dirblocks)
2314
def test_discard_renamed(self):
2315
null_stat = dirstate.DirState.NULLSTAT
2316
present_dir = ('d', '', 0, False, null_stat)
2317
present_file = ('f', '', 0, False, null_stat)
2318
absent = dirstate.DirState.NULL_PARENT_DETAILS
2319
root_key = ('', '', 'a-root-value')
2320
file_in_root_key = ('', 'file-in-root', 'a-file-id')
2321
# Renamed relative to parent
2322
file_rename_s_key = ('', 'file-s', 'b-file-id')
2323
file_rename_t_key = ('', 'file-t', 'b-file-id')
2324
# And one that is renamed between the parents, but absent in this
2325
key_in_1 = ('', 'file-in-1', 'c-file-id')
2326
key_in_2 = ('', 'file-in-2', 'c-file-id')
2329
('', [(root_key, [present_dir, present_dir, present_dir])]),
2331
[absent, present_file, ('r', 'file-in-2', 'c-file-id')]),
2333
[absent, ('r', 'file-in-1', 'c-file-id'), present_file]),
2335
[present_file, present_file, present_file]),
2337
[('r', 'file-t', 'b-file-id'), absent, present_file]),
2339
[present_file, absent, ('r', 'file-s', 'b-file-id')]),
2343
('', [(root_key, [present_dir, present_dir])]),
2344
('', [(key_in_1, [absent, present_file]),
2345
(file_in_root_key, [present_file, present_file]),
2346
(file_rename_t_key, [present_file, absent]),
2349
state = self.create_empty_dirstate()
2350
self.addCleanup(state.unlock)
2351
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2354
state._discard_merge_parents()
2356
self.assertEqual(exp_dirblocks, state._dirblocks)
2358
def test_discard_all_subdir(self):
2359
null_stat = dirstate.DirState.NULLSTAT
2360
present_dir = ('d', '', 0, False, null_stat)
2361
present_file = ('f', '', 0, False, null_stat)
2362
absent = dirstate.DirState.NULL_PARENT_DETAILS
2363
root_key = ('', '', 'a-root-value')
2364
subdir_key = ('', 'sub', 'dir-id')
2365
child1_key = ('sub', 'child1', 'child1-id')
2366
child2_key = ('sub', 'child2', 'child2-id')
2367
child3_key = ('sub', 'child3', 'child3-id')
2370
('', [(root_key, [present_dir, present_dir, present_dir])]),
2371
('', [(subdir_key, [present_dir, present_dir, present_dir])]),
2372
('sub', [(child1_key, [absent, absent, present_file]),
2373
(child2_key, [absent, absent, present_file]),
2374
(child3_key, [absent, absent, present_file]),
2378
('', [(root_key, [present_dir, present_dir])]),
2379
('', [(subdir_key, [present_dir, present_dir])]),
2382
state = self.create_empty_dirstate()
2383
self.addCleanup(state.unlock)
2384
state._set_data(['parent-id', 'merged-id'], dirblocks[:])
2387
state._discard_merge_parents()
2389
self.assertEqual(exp_dirblocks, state._dirblocks)
2392
class Test_InvEntryToDetails(tests.TestCase):
2394
def assertDetails(self, expected, inv_entry):
2395
details = dirstate.DirState._inv_entry_to_details(inv_entry)
2396
self.assertEqual(expected, details)
2397
# details should always allow join() and always be a plain str when
2399
(minikind, fingerprint, size, executable, tree_data) = details
2400
self.assertIsInstance(minikind, str)
2401
self.assertIsInstance(fingerprint, str)
2402
self.assertIsInstance(tree_data, str)
2404
def test_unicode_symlink(self):
2405
inv_entry = inventory.InventoryLink('link-file-id',
2406
u'nam\N{Euro Sign}e',
2408
inv_entry.revision = 'link-revision-id'
2409
target = u'link-targ\N{Euro Sign}t'
2410
inv_entry.symlink_target = target
2411
self.assertDetails(('l', target.encode('UTF-8'), 0, False,
2412
'link-revision-id'), inv_entry)
2415
class TestSHA1Provider(tests.TestCaseInTempDir):
2417
def test_sha1provider_is_an_interface(self):
2418
p = dirstate.SHA1Provider()
2419
self.assertRaises(NotImplementedError, p.sha1, "foo")
2420
self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
2422
def test_defaultsha1provider_sha1(self):
2423
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2424
self.build_tree_contents([('foo', text)])
2425
expected_sha = osutils.sha_string(text)
2426
p = dirstate.DefaultSHA1Provider()
2427
self.assertEqual(expected_sha, p.sha1('foo'))
2429
def test_defaultsha1provider_stat_and_sha1(self):
2430
text = 'test\r\nwith\nall\rpossible line endings\r\n'
2431
self.build_tree_contents([('foo', text)])
2432
expected_sha = osutils.sha_string(text)
2433
p = dirstate.DefaultSHA1Provider()
2434
statvalue, sha1 = p.stat_and_sha1('foo')
2435
self.assertTrue(len(statvalue) >= 10)
2436
self.assertEqual(len(text), statvalue.st_size)
2437
self.assertEqual(expected_sha, sha1)
2440
class _Repo(object):
2441
"""A minimal api to get InventoryRevisionTree to work."""
2444
default_format = bzrdir.format_registry.make_bzrdir('default')
2445
self._format = default_format.repository_format
2447
def lock_read(self):
2454
class TestUpdateBasisByDelta(tests.TestCase):
2456
def path_to_ie(self, path, file_id, rev_id, dir_ids):
2457
if path.endswith('/'):
2462
dirname, basename = osutils.split(path)
2464
dir_id = dir_ids[dirname]
2466
dir_id = osutils.basename(dirname) + '-id'
2468
ie = inventory.InventoryDirectory(file_id, basename, dir_id)
2469
dir_ids[path] = file_id
2471
ie = inventory.InventoryFile(file_id, basename, dir_id)
2474
ie.revision = rev_id
2477
def create_tree_from_shape(self, rev_id, shape):
2478
dir_ids = {'': 'root-id'}
2479
inv = inventory.Inventory('root-id', rev_id)
2480
for path, file_id in shape:
2482
# Replace the root entry
2483
del inv._byid[inv.root.file_id]
2484
inv.root.file_id = file_id
2485
inv._byid[file_id] = inv.root
2486
dir_ids[''] = file_id
2488
inv.add(self.path_to_ie(path, file_id, rev_id, dir_ids))
2489
return revisiontree.InventoryRevisionTree(_Repo(), inv, rev_id)
2491
def create_empty_dirstate(self):
2492
fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
2493
self.addCleanup(os.remove, path)
2495
state = dirstate.DirState.initialize(path)
2496
self.addCleanup(state.unlock)
2499
def create_inv_delta(self, delta, rev_id):
2500
"""Translate a 'delta shape' into an actual InventoryDelta"""
2501
dir_ids = {'': 'root-id'}
2503
for old_path, new_path, file_id in delta:
2504
if old_path is not None and old_path.endswith('/'):
2505
# Don't have to actually do anything for this, because only
2506
# new_path creates InventoryEntries
2507
old_path = old_path[:-1]
2508
if new_path is None: # Delete
2509
inv_delta.append((old_path, None, file_id, None))
2511
ie = self.path_to_ie(new_path, file_id, rev_id, dir_ids)
2512
inv_delta.append((old_path, new_path, file_id, ie))
2515
def assertUpdate(self, active, basis, target):
2516
"""Assert that update_basis_by_delta works how we want.
2518
Set up a DirState object with active_shape for tree 0, basis_shape for
2519
tree 1. Then apply the delta from basis_shape to target_shape,
2520
and assert that the DirState is still valid, and that its stored
2521
content matches the target_shape.
2523
active_tree = self.create_tree_from_shape('active', active)
2524
basis_tree = self.create_tree_from_shape('basis', basis)
2525
target_tree = self.create_tree_from_shape('target', target)
2526
state = self.create_empty_dirstate()
2527
state.set_state_from_scratch(active_tree.inventory,
2528
[('basis', basis_tree)], [])
2529
delta = target_tree.inventory._make_delta(basis_tree.inventory)
2530
state.update_basis_by_delta(delta, 'target')
2532
dirstate_tree = workingtree_4.DirStateRevisionTree(state,
2534
# The target now that delta has been applied should match the
2536
self.assertEqual([], list(dirstate_tree.iter_changes(target_tree)))
2537
# And the dirblock state should be identical to the state if we created
2539
state2 = self.create_empty_dirstate()
2540
state2.set_state_from_scratch(active_tree.inventory,
2541
[('target', target_tree)], [])
2542
self.assertEqual(state2._dirblocks, state._dirblocks)
2545
def assertBadDelta(self, active, basis, delta):
2546
"""Test that we raise InconsistentDelta when appropriate.
2548
:param active: The active tree shape
2549
:param basis: The basis tree shape
2550
:param delta: A description of the delta to apply. Similar to the form
2551
for regular inventory deltas, but omitting the InventoryEntry.
2552
So adding a file is: (None, 'path', 'file-id')
2553
Adding a directory is: (None, 'path/', 'dir-id')
2554
Renaming a dir is: ('old/', 'new/', 'dir-id')
2557
active_tree = self.create_tree_from_shape('active', active)
2558
basis_tree = self.create_tree_from_shape('basis', basis)
2559
inv_delta = self.create_inv_delta(delta, 'target')
2560
state = self.create_empty_dirstate()
2561
state.set_state_from_scratch(active_tree.inventory,
2562
[('basis', basis_tree)], [])
2563
self.assertRaises(errors.InconsistentDelta,
2564
state.update_basis_by_delta, inv_delta, 'target')
2566
## state.update_basis_by_delta(inv_delta, 'target')
2567
## except errors.InconsistentDelta, e:
2568
## import pdb; pdb.set_trace()
2570
## import pdb; pdb.set_trace()
2571
self.assertTrue(state._changes_aborted)
2573
def test_remove_file_matching_active_state(self):
2574
state = self.assertUpdate(
2576
basis =[('file', 'file-id')],
2580
def test_remove_file_present_in_active_state(self):
2581
state = self.assertUpdate(
2582
active=[('file', 'file-id')],
2583
basis =[('file', 'file-id')],
2587
def test_remove_file_present_elsewhere_in_active_state(self):
2588
state = self.assertUpdate(
2589
active=[('other-file', 'file-id')],
2590
basis =[('file', 'file-id')],
2594
def test_remove_file_active_state_has_diff_file(self):
2595
state = self.assertUpdate(
2596
active=[('file', 'file-id-2')],
2597
basis =[('file', 'file-id')],
2601
def test_remove_file_active_state_has_diff_file_and_file_elsewhere(self):
2602
state = self.assertUpdate(
2603
active=[('file', 'file-id-2'),
2604
('other-file', 'file-id')],
2605
basis =[('file', 'file-id')],
2609
def test_add_file_matching_active_state(self):
2610
state = self.assertUpdate(
2611
active=[('file', 'file-id')],
2613
target=[('file', 'file-id')],
2616
def test_add_file_missing_in_active_state(self):
2617
state = self.assertUpdate(
2620
target=[('file', 'file-id')],
2623
def test_add_file_elsewhere_in_active_state(self):
2624
state = self.assertUpdate(
2625
active=[('other-file', 'file-id')],
2627
target=[('file', 'file-id')],
2630
def test_add_file_active_state_has_diff_file_and_file_elsewhere(self):
2631
state = self.assertUpdate(
2632
active=[('other-file', 'file-id'),
2633
('file', 'file-id-2')],
2635
target=[('file', 'file-id')],
2638
def test_rename_file_matching_active_state(self):
2639
state = self.assertUpdate(
2640
active=[('other-file', 'file-id')],
2641
basis =[('file', 'file-id')],
2642
target=[('other-file', 'file-id')],
2645
def test_rename_file_missing_in_active_state(self):
2646
state = self.assertUpdate(
2648
basis =[('file', 'file-id')],
2649
target=[('other-file', 'file-id')],
2652
def test_rename_file_present_elsewhere_in_active_state(self):
2653
state = self.assertUpdate(
2654
active=[('third', 'file-id')],
2655
basis =[('file', 'file-id')],
2656
target=[('other-file', 'file-id')],
2659
def test_rename_file_active_state_has_diff_source_file(self):
2660
state = self.assertUpdate(
2661
active=[('file', 'file-id-2')],
2662
basis =[('file', 'file-id')],
2663
target=[('other-file', 'file-id')],
2666
def test_rename_file_active_state_has_diff_target_file(self):
2667
state = self.assertUpdate(
2668
active=[('other-file', 'file-id-2')],
2669
basis =[('file', 'file-id')],
2670
target=[('other-file', 'file-id')],
2673
def test_rename_file_active_has_swapped_files(self):
2674
state = self.assertUpdate(
2675
active=[('file', 'file-id'),
2676
('other-file', 'file-id-2')],
2677
basis= [('file', 'file-id'),
2678
('other-file', 'file-id-2')],
2679
target=[('file', 'file-id-2'),
2680
('other-file', 'file-id')])
2682
def test_rename_file_basis_has_swapped_files(self):
2683
state = self.assertUpdate(
2684
active=[('file', 'file-id'),
2685
('other-file', 'file-id-2')],
2686
basis= [('file', 'file-id-2'),
2687
('other-file', 'file-id')],
2688
target=[('file', 'file-id'),
2689
('other-file', 'file-id-2')])
2691
def test_rename_directory_with_contents(self):
2692
state = self.assertUpdate( # active matches basis
2693
active=[('dir1/', 'dir-id'),
2694
('dir1/file', 'file-id')],
2695
basis= [('dir1/', 'dir-id'),
2696
('dir1/file', 'file-id')],
2697
target=[('dir2/', 'dir-id'),
2698
('dir2/file', 'file-id')])
2699
state = self.assertUpdate( # active matches target
2700
active=[('dir2/', 'dir-id'),
2701
('dir2/file', 'file-id')],
2702
basis= [('dir1/', 'dir-id'),
2703
('dir1/file', 'file-id')],
2704
target=[('dir2/', 'dir-id'),
2705
('dir2/file', 'file-id')])
2706
state = self.assertUpdate( # active empty
2708
basis= [('dir1/', 'dir-id'),
2709
('dir1/file', 'file-id')],
2710
target=[('dir2/', 'dir-id'),
2711
('dir2/file', 'file-id')])
2712
state = self.assertUpdate( # active present at other location
2713
active=[('dir3/', 'dir-id'),
2714
('dir3/file', 'file-id')],
2715
basis= [('dir1/', 'dir-id'),
2716
('dir1/file', 'file-id')],
2717
target=[('dir2/', 'dir-id'),
2718
('dir2/file', 'file-id')])
2719
state = self.assertUpdate( # active has different ids
2720
active=[('dir1/', 'dir1-id'),
2721
('dir1/file', 'file1-id'),
2722
('dir2/', 'dir2-id'),
2723
('dir2/file', 'file2-id')],
2724
basis= [('dir1/', 'dir-id'),
2725
('dir1/file', 'file-id')],
2726
target=[('dir2/', 'dir-id'),
2727
('dir2/file', 'file-id')])
2729
def test_invalid_file_not_present(self):
2730
state = self.assertBadDelta(
2731
active=[('file', 'file-id')],
2732
basis= [('file', 'file-id')],
2733
delta=[('other-file', 'file', 'file-id')])
2735
def test_invalid_new_id_same_path(self):
2736
# The bad entry comes after
2737
state = self.assertBadDelta(
2738
active=[('file', 'file-id')],
2739
basis= [('file', 'file-id')],
2740
delta=[(None, 'file', 'file-id-2')])
2741
# The bad entry comes first
2742
state = self.assertBadDelta(
2743
active=[('file', 'file-id-2')],
2744
basis=[('file', 'file-id-2')],
2745
delta=[(None, 'file', 'file-id')])
2747
def test_invalid_existing_id(self):
2748
state = self.assertBadDelta(
2749
active=[('file', 'file-id')],
2750
basis= [('file', 'file-id')],
2751
delta=[(None, 'file', 'file-id')])
2753
def test_invalid_parent_missing(self):
2754
state = self.assertBadDelta(
2757
delta=[(None, 'path/path2', 'file-id')])
2758
# Note: we force the active tree to have the directory, by knowing how
2759
# path_to_ie handles entries with missing parents
2760
state = self.assertBadDelta(
2761
active=[('path/', 'path-id')],
2763
delta=[(None, 'path/path2', 'file-id')])
2764
state = self.assertBadDelta(
2765
active=[('path/', 'path-id'),
2766
('path/path2', 'file-id')],
2768
delta=[(None, 'path/path2', 'file-id')])
2770
def test_renamed_dir_same_path(self):
2771
# We replace the parent directory, with another parent dir. But the C
2772
# file doesn't look like it has been moved.
2773
state = self.assertUpdate(# Same as basis
2774
active=[('dir/', 'A-id'),
2776
basis= [('dir/', 'A-id'),
2778
target=[('dir/', 'C-id'),
2780
state = self.assertUpdate(# Same as target
2781
active=[('dir/', 'C-id'),
2783
basis= [('dir/', 'A-id'),
2785
target=[('dir/', 'C-id'),
2787
state = self.assertUpdate(# empty active
2789
basis= [('dir/', 'A-id'),
2791
target=[('dir/', 'C-id'),
2793
state = self.assertUpdate(# different active
2794
active=[('dir/', 'D-id'),
2796
basis= [('dir/', 'A-id'),
2798
target=[('dir/', 'C-id'),
2801
def test_parent_child_swap(self):
2802
state = self.assertUpdate(# Same as basis
2803
active=[('A/', 'A-id'),
2806
basis= [('A/', 'A-id'),
2809
target=[('A/', 'B-id'),
2812
state = self.assertUpdate(# Same as target
2813
active=[('A/', 'B-id'),
2816
basis= [('A/', 'A-id'),
2819
target=[('A/', 'B-id'),
2822
state = self.assertUpdate(# empty active
2824
basis= [('A/', 'A-id'),
2827
target=[('A/', 'B-id'),
2830
state = self.assertUpdate(# different active
2831
active=[('D/', 'A-id'),
2834
basis= [('A/', 'A-id'),
2837
target=[('A/', 'B-id'),
2841
def test_change_root_id(self):
2842
state = self.assertUpdate( # same as basis
2843
active=[('', 'root-id'),
2844
('file', 'file-id')],
2845
basis= [('', 'root-id'),
2846
('file', 'file-id')],
2847
target=[('', 'target-root-id'),
2848
('file', 'file-id')])
2849
state = self.assertUpdate( # same as target
2850
active=[('', 'target-root-id'),
2851
('file', 'file-id')],
2852
basis= [('', 'root-id'),
2853
('file', 'file-id')],
2854
target=[('', 'target-root-id'),
2855
('file', 'root-id')])
2856
state = self.assertUpdate( # all different
2857
active=[('', 'active-root-id'),
2858
('file', 'file-id')],
2859
basis= [('', 'root-id'),
2860
('file', 'file-id')],
2861
target=[('', 'target-root-id'),
2862
('file', 'root-id')])
2864
def test_change_file_absent_in_active(self):
2865
state = self.assertUpdate(
2867
basis= [('file', 'file-id')],
2868
target=[('file', 'file-id')])
2870
def test_invalid_changed_file(self):
2871
state = self.assertBadDelta( # Not present in basis
2872
active=[('file', 'file-id')],
2874
delta=[('file', 'file', 'file-id')])
2875
state = self.assertBadDelta( # present at another location in basis
2876
active=[('file', 'file-id')],
2877
basis= [('other-file', 'file-id')],
2878
delta=[('file', 'file', 'file-id')])