758
691
# This will unlock it
759
692
self.check_state_with_reopen(expected_result, state)
761
def test_set_state_from_inventory_preserves_hashcache(self):
762
# https://bugs.launchpad.net/bzr/+bug/146176
763
# set_state_from_inventory should preserve the stat and hash value for
764
# workingtree files that are not changed by the inventory.
766
tree = self.make_branch_and_tree('.')
767
# depends on the default format using dirstate...
770
# make a dirstate with some valid hashcache data
771
# file on disk, but that's not needed for this test
772
foo_contents = 'contents of foo'
773
self.build_tree_contents([('foo', foo_contents)])
774
tree.add('foo', 'foo-id')
776
foo_stat = os.stat('foo')
777
foo_packed = dirstate.pack_stat(foo_stat)
778
foo_sha = osutils.sha_string(foo_contents)
779
foo_size = len(foo_contents)
781
# should not be cached yet, because the file's too fresh
783
(('', 'foo', 'foo-id',),
784
[('f', '', 0, False, dirstate.DirState.NULLSTAT)]),
785
tree._dirstate._get_entry(0, 'foo-id'))
786
# poke in some hashcache information - it wouldn't normally be
787
# stored because it's too fresh
788
tree._dirstate.update_minimal(
789
('', 'foo', 'foo-id'),
790
'f', False, foo_sha, foo_packed, foo_size, 'foo')
791
# now should be cached
793
(('', 'foo', 'foo-id',),
794
[('f', foo_sha, foo_size, False, foo_packed)]),
795
tree._dirstate._get_entry(0, 'foo-id'))
797
# extract the inventory, and add something to it
798
inv = tree._get_inventory()
799
# should see the file we poked in...
800
self.assertTrue(inv.has_id('foo-id'))
801
self.assertTrue(inv.has_filename('foo'))
802
inv.add_path('bar', 'file', 'bar-id')
803
tree._dirstate._validate()
804
# this used to cause it to lose its hashcache
805
tree._dirstate.set_state_from_inventory(inv)
806
tree._dirstate._validate()
812
# now check that the state still has the original hashcache value
813
state = tree._dirstate
815
foo_tuple = state._get_entry(0, path_utf8='foo')
817
(('', 'foo', 'foo-id',),
818
[('f', foo_sha, len(foo_contents), False,
819
dirstate.pack_stat(foo_stat))]),
824
def test_set_state_from_inventory_mixed_paths(self):
825
tree1 = self.make_branch_and_tree('tree1')
826
self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
827
'tree1/a/b/foo', 'tree1/a-b/bar'])
830
tree1.add(['a', 'a/b', 'a-b', 'a/b/foo', 'a-b/bar'],
831
['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
832
tree1.commit('rev1', rev_id='rev1')
833
root_id = tree1.get_root_id()
834
inv = tree1.inventory
837
expected_result1 = [('', '', root_id, 'd'),
838
('', 'a', 'a-id', 'd'),
839
('', 'a-b', 'a-b-id', 'd'),
840
('a', 'b', 'b-id', 'd'),
841
('a/b', 'foo', 'foo-id', 'f'),
842
('a-b', 'bar', 'bar-id', 'f'),
844
expected_result2 = [('', '', root_id, 'd'),
845
('', 'a', 'a-id', 'd'),
846
('', 'a-b', 'a-b-id', 'd'),
847
('a-b', 'bar', 'bar-id', 'f'),
849
state = dirstate.DirState.initialize('dirstate')
851
state.set_state_from_inventory(inv)
853
for entry in state._iter_entries():
854
values.append(entry[0] + entry[1][0][:1])
855
self.assertEqual(expected_result1, values)
857
state.set_state_from_inventory(inv)
859
for entry in state._iter_entries():
860
values.append(entry[0] + entry[1][0][:1])
861
self.assertEqual(expected_result2, values)
865
694
def test_set_path_id_no_parents(self):
866
695
"""The id of a path can be changed trivally with no parents."""
867
696
state = dirstate.DirState.initialize('dirstate')
869
698
# check precondition to be sure the state does change appropriately.
870
root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
871
self.assertEqual([root_entry], list(state._iter_entries()))
872
self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
873
self.assertEqual(root_entry,
874
state._get_entry(0, fileid_utf8='TREE_ROOT'))
875
self.assertEqual((None, None),
876
state._get_entry(0, fileid_utf8='second-root-id'))
877
state.set_path_id('', 'second-root-id')
878
new_root_entry = (('', '', 'second-root-id'),
879
[('d', '', 0, False, 'x'*32)])
880
expected_rows = [new_root_entry]
700
[(('', '', 'TREE_ROOT'), [('d', '', 0, False,
701
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
702
list(state._iter_entries()))
703
state.set_path_id('', 'foobarbaz')
705
(('', '', 'foobarbaz'), [('d', '', 0, False,
706
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
881
707
self.assertEqual(expected_rows, list(state._iter_entries()))
882
self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
883
self.assertEqual(new_root_entry,
884
state._get_entry(0, fileid_utf8='second-root-id'))
885
self.assertEqual((None, None),
886
state._get_entry(0, fileid_utf8='TREE_ROOT'))
887
708
# should work across save too
907
728
state._validate()
909
730
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')
731
state.set_path_id('', 'foobarbaz')
919
732
state._validate()
920
733
# 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]
735
(('', '', 'TREE_ROOT'),
736
[('a', '', 0, False, ''),
737
('d', '', 0, False, 'parent-revid'),
739
(('', '', 'foobarbaz'),
740
[('d', '', 0, False, ''),
741
('a', '', 0, False, ''),
928
744
state._validate()
929
745
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
746
# should work across save too
1506
class TestIterChildEntries(TestCaseWithDirState):
1508
def create_dirstate_with_two_trees(self):
1509
"""This dirstate contains multiple files and directories.
1519
b/h\xc3\xa5 h-\xc3\xa5-file #This is u'\xe5' encoded into utf-8
1521
Notice that a/e is an empty directory.
1523
There is one parent tree, which has the same shape with the following variations:
1524
b/g in the parent is gone.
1525
b/h in the parent has a different id
1526
b/i is new in the parent
1527
c is renamed to b/j in the parent
1529
:return: The dirstate, still write-locked.
1531
packed_stat = 'AAAAREUHaIpFB2iKAAADAQAtkqUAAIGk'
1532
null_sha = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
1533
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1534
root_entry = ('', '', 'a-root-value'), [
1535
('d', '', 0, False, packed_stat),
1536
('d', '', 0, False, 'parent-revid'),
1538
a_entry = ('', 'a', 'a-dir'), [
1539
('d', '', 0, False, packed_stat),
1540
('d', '', 0, False, 'parent-revid'),
1542
b_entry = ('', 'b', 'b-dir'), [
1543
('d', '', 0, False, packed_stat),
1544
('d', '', 0, False, 'parent-revid'),
1546
c_entry = ('', 'c', 'c-file'), [
1547
('f', null_sha, 10, False, packed_stat),
1548
('r', 'b/j', 0, False, ''),
1550
d_entry = ('', 'd', 'd-file'), [
1551
('f', null_sha, 20, False, packed_stat),
1552
('f', 'd', 20, False, 'parent-revid'),
1554
e_entry = ('a', 'e', 'e-dir'), [
1555
('d', '', 0, False, packed_stat),
1556
('d', '', 0, False, 'parent-revid'),
1558
f_entry = ('a', 'f', 'f-file'), [
1559
('f', null_sha, 30, False, packed_stat),
1560
('f', 'f', 20, False, 'parent-revid'),
1562
g_entry = ('b', 'g', 'g-file'), [
1563
('f', null_sha, 30, False, packed_stat),
1564
NULL_PARENT_DETAILS,
1566
h_entry1 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file1'), [
1567
('f', null_sha, 40, False, packed_stat),
1568
NULL_PARENT_DETAILS,
1570
h_entry2 = ('b', 'h\xc3\xa5', 'h-\xc3\xa5-file2'), [
1571
NULL_PARENT_DETAILS,
1572
('f', 'h', 20, False, 'parent-revid'),
1574
i_entry = ('b', 'i', 'i-file'), [
1575
NULL_PARENT_DETAILS,
1576
('f', 'h', 20, False, 'parent-revid'),
1578
j_entry = ('b', 'j', 'c-file'), [
1579
('r', 'c', 0, False, ''),
1580
('f', 'j', 20, False, 'parent-revid'),
1583
dirblocks.append(('', [root_entry]))
1584
dirblocks.append(('', [a_entry, b_entry, c_entry, d_entry]))
1585
dirblocks.append(('a', [e_entry, f_entry]))
1586
dirblocks.append(('b', [g_entry, h_entry1, h_entry2, i_entry, j_entry]))
1587
state = dirstate.DirState.initialize('dirstate')
1590
state._set_data(['parent'], dirblocks)
1594
return state, dirblocks
1596
def test_iter_children_b(self):
1597
state, dirblocks = self.create_dirstate_with_two_trees()
1598
self.addCleanup(state.unlock)
1599
expected_result = []
1600
expected_result.append(dirblocks[3][1][2]) # h2
1601
expected_result.append(dirblocks[3][1][3]) # i
1602
expected_result.append(dirblocks[3][1][4]) # j
1603
self.assertEqual(expected_result,
1604
list(state._iter_child_entries(1, 'b')))
1606
def test_iter_child_root(self):
1607
state, dirblocks = self.create_dirstate_with_two_trees()
1608
self.addCleanup(state.unlock)
1609
expected_result = []
1610
expected_result.append(dirblocks[1][1][0]) # a
1611
expected_result.append(dirblocks[1][1][1]) # b
1612
expected_result.append(dirblocks[1][1][3]) # d
1613
expected_result.append(dirblocks[2][1][0]) # e
1614
expected_result.append(dirblocks[2][1][1]) # f
1615
expected_result.append(dirblocks[3][1][2]) # h2
1616
expected_result.append(dirblocks[3][1][3]) # i
1617
expected_result.append(dirblocks[3][1][4]) # j
1618
self.assertEqual(expected_result,
1619
list(state._iter_child_entries(1, '')))
1622
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1283
class TestDirstateSortOrder(TestCaseWithTransport):
1623
1284
"""Test that DirState adds entries in the right order."""
1625
1286
def test_add_sorting(self):
1735
1393
self.st_ino = ino
1736
1394
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):
1397
class TestUpdateEntry(TestCaseWithDirState):
1398
"""Test the DirState.update_entry functions"""
1400
def get_state_with_a(self):
1401
"""Create a DirState tracking a single object named 'a'"""
1402
state = InstrumentedDirState.initialize('dirstate')
1403
self.addCleanup(state.unlock)
1404
state.add('a', 'a-id', 'file', None, '')
1405
entry = state._get_entry(0, path_utf8='a')
1408
def test_update_entry(self):
1409
state, entry = self.get_state_with_a()
1410
self.build_tree(['a'])
1411
# Add one where we don't provide the stat or sha already
1412
self.assertEqual(('', 'a', 'a-id'), entry[0])
1413
self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1415
# Flush the buffers to disk
1417
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1418
state._dirblock_state)
1420
stat_value = os.lstat('a')
1421
packed_stat = dirstate.pack_stat(stat_value)
1422
link_or_sha1 = state.update_entry(entry, abspath='a',
1423
stat_value=stat_value)
1424
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1427
# The dirblock entry should not cache the file's sha1
1428
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1430
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1431
state._dirblock_state)
1432
mode = stat_value.st_mode
1433
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1436
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1437
state._dirblock_state)
1439
# If we do it again right away, we don't know if the file has changed
1440
# so we will re-read the file. Roll the clock back so the file is
1441
# guaranteed to look too new.
1442
state.adjust_time(-10)
1444
link_or_sha1 = state.update_entry(entry, abspath='a',
1445
stat_value=stat_value)
1446
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1447
('sha1', 'a'), ('is_exec', mode, False),
1449
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1451
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1452
state._dirblock_state)
1453
self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1457
# However, if we move the clock forward so the file is considered
1458
# "stable", it should just cache the value.
1459
state.adjust_time(+20)
1460
link_or_sha1 = state.update_entry(entry, abspath='a',
1461
stat_value=stat_value)
1462
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1464
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1465
('sha1', 'a'), ('is_exec', mode, False),
1466
('sha1', 'a'), ('is_exec', mode, False),
1468
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1471
# Subsequent calls will just return the cached value
1472
link_or_sha1 = state.update_entry(entry, abspath='a',
1473
stat_value=stat_value)
1474
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1476
self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1477
('sha1', 'a'), ('is_exec', mode, False),
1478
('sha1', 'a'), ('is_exec', mode, False),
1480
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1483
def test_update_entry_symlink(self):
1484
"""Update entry should read symlinks."""
1485
if not osutils.has_symlinks():
1486
# PlatformDeficiency / TestSkipped
1487
raise TestSkipped("No symlink support")
1488
state, entry = self.get_state_with_a()
1490
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1491
state._dirblock_state)
1492
os.symlink('target', 'a')
1494
state.adjust_time(-10) # Make the symlink look new
1495
stat_value = os.lstat('a')
1496
packed_stat = dirstate.pack_stat(stat_value)
1497
link_or_sha1 = state.update_entry(entry, abspath='a',
1498
stat_value=stat_value)
1499
self.assertEqual('target', link_or_sha1)
1500
self.assertEqual([('read_link', 'a', '')], state._log)
1501
# Dirblock is not updated (the link is too new)
1502
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1504
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1505
state._dirblock_state)
1507
# Because the stat_value looks new, we should re-read the target
1508
link_or_sha1 = state.update_entry(entry, abspath='a',
1509
stat_value=stat_value)
1510
self.assertEqual('target', link_or_sha1)
1511
self.assertEqual([('read_link', 'a', ''),
1512
('read_link', 'a', ''),
1514
self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1516
state.adjust_time(+20) # Skip into the future, all files look old
1517
link_or_sha1 = state.update_entry(entry, abspath='a',
1518
stat_value=stat_value)
1519
self.assertEqual('target', link_or_sha1)
1520
# We need to re-read the link because only now can we cache it
1521
self.assertEqual([('read_link', 'a', ''),
1522
('read_link', 'a', ''),
1523
('read_link', 'a', ''),
1525
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1528
# Another call won't re-read the link
1529
self.assertEqual([('read_link', 'a', ''),
1530
('read_link', 'a', ''),
1531
('read_link', 'a', ''),
1533
link_or_sha1 = state.update_entry(entry, abspath='a',
1534
stat_value=stat_value)
1535
self.assertEqual('target', link_or_sha1)
1536
self.assertEqual([('l', 'target', 6, False, packed_stat)],
1539
def do_update_entry(self, state, entry, abspath):
1540
stat_value = os.lstat(abspath)
1541
return state.update_entry(entry, abspath, stat_value)
1543
def test_update_entry_dir(self):
1544
state, entry = self.get_state_with_a()
1545
self.build_tree(['a/'])
1546
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1548
def test_update_entry_dir_unchanged(self):
1549
state, entry = self.get_state_with_a()
1550
self.build_tree(['a/'])
1551
state.adjust_time(+20)
1552
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1553
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1554
state._dirblock_state)
1556
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1557
state._dirblock_state)
1558
self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1559
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1560
state._dirblock_state)
1562
def test_update_entry_file_unchanged(self):
1563
state, entry = self.get_state_with_a()
1564
self.build_tree(['a'])
1565
sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1566
state.adjust_time(+20)
1567
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1568
self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1569
state._dirblock_state)
1571
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1572
state._dirblock_state)
1573
self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1574
self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1575
state._dirblock_state)
1577
def create_and_test_file(self, state, entry):
1578
"""Create a file at 'a' and verify the state finds it.
1580
The state should already be versioning *something* at 'a'. This makes
1581
sure that state.update_entry recognizes it as a file.
1583
self.build_tree(['a'])
1584
stat_value = os.lstat('a')
1585
packed_stat = dirstate.pack_stat(stat_value)
1587
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1588
self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1590
self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1594
def create_and_test_dir(self, state, entry):
1595
"""Create a directory at 'a' and verify the state finds it.
1597
The state should already be versioning *something* at 'a'. This makes
1598
sure that state.update_entry recognizes it as a directory.
1600
self.build_tree(['a/'])
1601
stat_value = os.lstat('a')
1602
packed_stat = dirstate.pack_stat(stat_value)
1604
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1605
self.assertIs(None, link_or_sha1)
1606
self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1610
def create_and_test_symlink(self, state, entry):
1611
"""Create a symlink at 'a' and verify the state finds it.
1613
The state should already be versioning *something* at 'a'. This makes
1614
sure that state.update_entry recognizes it as a symlink.
1616
This should not be called if this platform does not have symlink
1619
# caller should care about skipping test on platforms without symlinks
1620
os.symlink('path/to/foo', 'a')
1622
stat_value = os.lstat('a')
1623
packed_stat = dirstate.pack_stat(stat_value)
1625
link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1626
self.assertEqual('path/to/foo', link_or_sha1)
1627
self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1631
def test_update_file_to_dir(self):
1632
"""If a file changes to a directory we return None for the sha.
1633
We also update the inventory record.
1635
state, entry = self.get_state_with_a()
1636
# The file sha1 won't be cached unless the file is old
1637
state.adjust_time(+10)
1638
self.create_and_test_file(state, entry)
1640
self.create_and_test_dir(state, entry)
1642
def test_update_file_to_symlink(self):
1643
"""File becomes a symlink"""
1644
if not osutils.has_symlinks():
1645
# PlatformDeficiency / TestSkipped
1646
raise TestSkipped("No symlink support")
1647
state, entry = self.get_state_with_a()
1648
# The file sha1 won't be cached unless the file is old
1649
state.adjust_time(+10)
1650
self.create_and_test_file(state, entry)
1652
self.create_and_test_symlink(state, entry)
1654
def test_update_dir_to_file(self):
1655
"""Directory becoming a file updates the entry."""
1656
state, entry = self.get_state_with_a()
1657
# The file sha1 won't be cached unless the file is old
1658
state.adjust_time(+10)
1659
self.create_and_test_dir(state, entry)
1661
self.create_and_test_file(state, entry)
1663
def test_update_dir_to_symlink(self):
1664
"""Directory becomes a symlink"""
1665
if not osutils.has_symlinks():
1666
# PlatformDeficiency / TestSkipped
1667
raise TestSkipped("No symlink support")
1668
state, entry = self.get_state_with_a()
1669
# The symlink target won't be cached if it isn't old
1670
state.adjust_time(+10)
1671
self.create_and_test_dir(state, entry)
1673
self.create_and_test_symlink(state, entry)
1675
def test_update_symlink_to_file(self):
1676
"""Symlink becomes a file"""
1677
if not has_symlinks():
1678
raise TestSkipped("No symlink support")
1679
state, entry = self.get_state_with_a()
1680
# The symlink and file info won't be cached unless old
1681
state.adjust_time(+10)
1682
self.create_and_test_symlink(state, entry)
1684
self.create_and_test_file(state, entry)
1686
def test_update_symlink_to_dir(self):
1687
"""Symlink becomes a directory"""
1688
if not has_symlinks():
1689
raise TestSkipped("No symlink support")
1690
state, entry = self.get_state_with_a()
1691
# The symlink target won't be cached if it isn't old
1692
state.adjust_time(+10)
1693
self.create_and_test_symlink(state, entry)
1695
self.create_and_test_dir(state, entry)
1697
def test__is_executable_win32(self):
1698
state, entry = self.get_state_with_a()
1699
self.build_tree(['a'])
1701
# Make sure we are using the win32 implementation of _is_executable
1702
state._is_executable = state._is_executable_win32
1704
# The file on disk is not executable, but we are marking it as though
1705
# it is. With _is_executable_win32 we ignore what is on disk.
1706
entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1708
stat_value = os.lstat('a')
1709
packed_stat = dirstate.pack_stat(stat_value)
1711
state.adjust_time(-10) # Make sure everything is new
1712
state.update_entry(entry, abspath='a', stat_value=stat_value)
1714
# The row is updated, but the executable bit stays set.
1715
self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1718
# Make the disk object look old enough to cache
1719
state.adjust_time(+20)
1720
digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1721
state.update_entry(entry, abspath='a', stat_value=stat_value)
1722
self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1725
class TestPackStat(TestCaseWithTransport):
1746
1727
def assertPackStat(self, expected, stat_value):
1747
1728
"""Check the packed and serialized form of a stat value."""
2009
class TestBisectDirblock(TestCase):
2010
"""Test that bisect_dirblock() returns the expected values.
2012
bisect_dirblock is intended to work like bisect.bisect_left() except it
2013
knows it is working on dirblocks and that dirblocks are sorted by ('path',
2014
'to', 'foo') chunks rather than by raw 'path/to/foo'.
2017
def assertBisect(self, dirblocks, split_dirblocks, path, *args, **kwargs):
2018
"""Assert that bisect_split works like bisect_left on the split paths.
2020
:param dirblocks: A list of (path, [info]) pairs.
2021
:param split_dirblocks: A list of ((split, path), [info]) pairs.
2022
:param path: The path we are indexing.
2024
All other arguments will be passed along.
2026
bisect_split_idx = dirstate.bisect_dirblock(dirblocks, path,
2028
split_dirblock = (path.split('/'), [])
2029
bisect_left_idx = bisect.bisect_left(split_dirblocks, split_dirblock,
2031
self.assertEqual(bisect_left_idx, bisect_split_idx,
2032
'bisect_split disagreed. %s != %s'
2034
% (bisect_left_idx, bisect_split_idx, path)
2037
def paths_to_dirblocks(self, paths):
2038
"""Convert a list of paths into dirblock form.
2040
Also, ensure that the paths are in proper sorted order.
2042
dirblocks = [(path, []) for path in paths]
2043
split_dirblocks = [(path.split('/'), []) for path in paths]
2044
self.assertEqual(sorted(split_dirblocks), split_dirblocks)
2045
return dirblocks, split_dirblocks
2047
def test_simple(self):
2048
"""In the simple case it works just like bisect_left"""
2049
paths = ['', 'a', 'b', 'c', 'd']
2050
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2052
self.assertBisect(dirblocks, split_dirblocks, path)
2053
self.assertBisect(dirblocks, split_dirblocks, '_')
2054
self.assertBisect(dirblocks, split_dirblocks, 'aa')
2055
self.assertBisect(dirblocks, split_dirblocks, 'bb')
2056
self.assertBisect(dirblocks, split_dirblocks, 'cc')
2057
self.assertBisect(dirblocks, split_dirblocks, 'dd')
2058
self.assertBisect(dirblocks, split_dirblocks, 'a/a')
2059
self.assertBisect(dirblocks, split_dirblocks, 'b/b')
2060
self.assertBisect(dirblocks, split_dirblocks, 'c/c')
2061
self.assertBisect(dirblocks, split_dirblocks, 'd/d')
2063
def test_involved(self):
2064
"""This is where bisect_left diverges slightly."""
2066
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
2067
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
2069
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
2070
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
2073
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2075
self.assertBisect(dirblocks, split_dirblocks, path)
2077
def test_involved_cached(self):
2078
"""This is where bisect_left diverges slightly."""
2080
'a/a', 'a/a/a', 'a/a/z', 'a/a-a', 'a/a-z',
2081
'a/z', 'a/z/a', 'a/z/z', 'a/z-a', 'a/z-z',
2083
'z', 'z/a/a', 'z/a/z', 'z/a-a', 'z/a-z',
2084
'z/z', 'z/z/a', 'z/z/z', 'z/z-a', 'z/z-z',
2088
dirblocks, split_dirblocks = self.paths_to_dirblocks(paths)
2090
self.assertBisect(dirblocks, split_dirblocks, path, cache=cache)
2035
2093
class TestDirstateValidation(TestCaseWithDirState):
2037
2095
def test_validate_correct_dirstate(self):
2087
2145
state._validate)
2088
2146
self.assertContainsRe(str(e),
2089
2147
'file a-id is absent in row')
2092
class TestDirstateTreeReference(TestCaseWithDirState):
2094
def test_reference_revision_is_none(self):
2095
tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
2096
subtree = self.make_branch_and_tree('tree/subtree',
2097
format='dirstate-with-subtree')
2098
subtree.set_root_id('subtree')
2099
tree.add_reference(subtree)
2101
state = dirstate.DirState.from_tree(tree, 'dirstate')
2102
key = ('', 'subtree', 'subtree')
2103
expected = ('', [(key,
2104
[('t', '', 0, False, 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])])
2107
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)