~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

  • Committer: Robert Collins
  • Date: 2010-04-08 04:34:03 UTC
  • mfrom: (5138 +trunk)
  • mto: This revision was merged to the branch mainline in revision 5139.
  • Revision ID: robertc@robertcollins.net-20100408043403-56z0d07vdqrx7f3t
Update bugfix for 528114 to trunk.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006-2010 Canonical Ltd
2
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
12
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
 
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
18
18
 
19
19
import bisect
20
20
import os
21
 
import time
22
21
 
23
22
from bzrlib import (
24
23
    dirstate,
25
24
    errors,
 
25
    inventory,
 
26
    memorytree,
26
27
    osutils,
 
28
    revision as _mod_revision,
 
29
    tests,
27
30
    )
28
 
from bzrlib.memorytree import MemoryTree
29
 
from bzrlib.tests import (
30
 
        SymlinkFeature,
31
 
        TestCase,
32
 
        TestCaseWithTransport,
33
 
        )
 
31
from bzrlib.tests import test_osutils
34
32
 
35
33
 
36
34
# TODO:
46
44
# set_path_id  setting id when state is in memory modified
47
45
 
48
46
 
49
 
class TestCaseWithDirState(TestCaseWithTransport):
 
47
def load_tests(basic_tests, module, loader):
 
48
    suite = loader.suiteClass()
 
49
    dir_reader_tests, remaining_tests = tests.split_suite_by_condition(
 
50
        basic_tests, tests.condition_isinstance(TestCaseWithDirState))
 
51
    tests.multiply_tests(dir_reader_tests,
 
52
                         test_osutils.dir_reader_scenarios(), suite)
 
53
    suite.addTest(remaining_tests)
 
54
    return suite
 
55
 
 
56
 
 
57
class TestCaseWithDirState(tests.TestCaseWithTransport):
50
58
    """Helper functions for creating DirState objects with various content."""
51
59
 
 
60
    # Set by load_tests
 
61
    _dir_reader_class = None
 
62
    _native_to_unicode = None # Not used yet
 
63
 
 
64
    def setUp(self):
 
65
        tests.TestCaseWithTransport.setUp(self)
 
66
 
 
67
        self.overrideAttr(osutils,
 
68
                          '_selected_dir_reader', self._dir_reader_class())
 
69
 
52
70
    def create_empty_dirstate(self):
53
71
        """Return a locked but empty dirstate"""
54
72
        state = dirstate.DirState.initialize('dirstate')
162
180
        """
163
181
        # The state should already be write locked, since we just had to do
164
182
        # some operation to get here.
165
 
        assert state._lock_token is not None
 
183
        self.assertTrue(state._lock_token is not None)
166
184
        try:
167
185
            self.assertEqual(expected_result[0],  state.get_parent_ids())
168
186
            # there should be no ghosts in this tree.
395
413
            (('', '', tree.get_root_id()), # common details
396
414
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
397
415
              ('d', '', 0, False, rev_id), # first parent details
398
 
              ('d', '', 0, False, rev_id2), # second parent details
 
416
              ('d', '', 0, False, rev_id), # second parent details
399
417
             ])])
400
418
        state = dirstate.DirState.from_tree(tree, 'dirstate')
401
419
        self.check_state_with_reopen(expected_result, state)
476
494
            (('', '', tree.get_root_id()), # common details
477
495
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
478
496
              ('d', '', 0, False, rev_id), # first parent details
479
 
              ('d', '', 0, False, rev_id2), # second parent details
 
497
              ('d', '', 0, False, rev_id), # second parent details
480
498
             ]),
481
499
            (('', 'a file', 'a-file-id'), # common
482
500
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
562
580
        state.lock_read()
563
581
        try:
564
582
            entry = state._get_entry(0, path_utf8='a-file')
565
 
            # The current sha1 sum should be empty
566
 
            self.assertEqual('', entry[1][0][1])
 
583
            # The current size should be 0 (default)
 
584
            self.assertEqual(0, entry[1][0][2])
567
585
            # We should have a real entry.
568
586
            self.assertNotEqual((None, None), entry)
569
587
            # Make sure everything is old enough
570
588
            state._sha_cutoff_time()
571
589
            state._cutoff_time += 10
572
 
            sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
573
 
            # We should have gotten a real sha1
574
 
            self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
575
 
                             sha1sum)
 
590
            # Change the file length
 
591
            self.build_tree_contents([('a-file', 'shorter')])
 
592
            sha1sum = dirstate.update_entry(state, entry, 'a-file',
 
593
                os.lstat('a-file'))
 
594
            # new file, no cached sha:
 
595
            self.assertEqual(None, sha1sum)
576
596
 
577
597
            # The dirblock has been updated
578
 
            self.assertEqual(sha1sum, entry[1][0][1])
 
598
            self.assertEqual(7, entry[1][0][2])
579
599
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
580
600
                             state._dirblock_state)
581
601
 
591
611
        state.lock_read()
592
612
        try:
593
613
            entry = state._get_entry(0, path_utf8='a-file')
594
 
            self.assertEqual(sha1sum, entry[1][0][1])
 
614
            self.assertEqual(7, entry[1][0][2])
595
615
        finally:
596
616
            state.unlock()
597
617
 
610
630
        state.lock_read()
611
631
        try:
612
632
            entry = state._get_entry(0, path_utf8='a-file')
613
 
            sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
614
 
            # We should have gotten a real sha1
615
 
            self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
616
 
                             sha1sum)
 
633
            sha1sum = dirstate.update_entry(state, entry, 'a-file',
 
634
                os.lstat('a-file'))
 
635
            # No sha - too new
 
636
            self.assertEqual(None, sha1sum)
617
637
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
618
638
                             state._dirblock_state)
619
639
 
636
656
                state2.unlock()
637
657
        finally:
638
658
            state.unlock()
639
 
        
 
659
 
640
660
        # The file on disk should not be modified.
641
661
        state = dirstate.DirState.on_file('dirstate')
642
662
        state.lock_read()
646
666
        finally:
647
667
            state.unlock()
648
668
 
 
669
    def test_save_refuses_if_changes_aborted(self):
 
670
        self.build_tree(['a-file', 'a-dir/'])
 
671
        state = dirstate.DirState.initialize('dirstate')
 
672
        try:
 
673
            # No stat and no sha1 sum.
 
674
            state.add('a-file', 'a-file-id', 'file', None, '')
 
675
            state.save()
 
676
        finally:
 
677
            state.unlock()
 
678
 
 
679
        # The dirstate should include TREE_ROOT and 'a-file' and nothing else
 
680
        expected_blocks = [
 
681
            ('', [(('', '', 'TREE_ROOT'),
 
682
                   [('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
 
683
            ('', [(('', 'a-file', 'a-file-id'),
 
684
                   [('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
 
685
        ]
 
686
 
 
687
        state = dirstate.DirState.on_file('dirstate')
 
688
        state.lock_write()
 
689
        try:
 
690
            state._read_dirblocks_if_needed()
 
691
            self.assertEqual(expected_blocks, state._dirblocks)
 
692
 
 
693
            # Now modify the state, but mark it as inconsistent
 
694
            state.add('a-dir', 'a-dir-id', 'directory', None, '')
 
695
            state._changes_aborted = True
 
696
            state.save()
 
697
        finally:
 
698
            state.unlock()
 
699
 
 
700
        state = dirstate.DirState.on_file('dirstate')
 
701
        state.lock_read()
 
702
        try:
 
703
            state._read_dirblocks_if_needed()
 
704
            self.assertEqual(expected_blocks, state._dirblocks)
 
705
        finally:
 
706
            state.unlock()
 
707
 
649
708
 
650
709
class TestDirStateInitialize(TestCaseWithDirState):
651
710
 
703
762
        # https://bugs.launchpad.net/bzr/+bug/146176
704
763
        # set_state_from_inventory should preserve the stat and hash value for
705
764
        # workingtree files that are not changed by the inventory.
706
 
       
 
765
 
707
766
        tree = self.make_branch_and_tree('.')
708
767
        # depends on the default format using dirstate...
709
768
        tree.lock_write()
710
769
        try:
711
 
            # make a dirstate with some valid hashcache data 
 
770
            # make a dirstate with some valid hashcache data
712
771
            # file on disk, but that's not needed for this test
713
772
            foo_contents = 'contents of foo'
714
773
            self.build_tree_contents([('foo', foo_contents)])
734
793
                (('', 'foo', 'foo-id',),
735
794
                 [('f', foo_sha, foo_size, False, foo_packed)]),
736
795
                tree._dirstate._get_entry(0, 'foo-id'))
737
 
           
 
796
 
738
797
            # extract the inventory, and add something to it
739
798
            inv = tree._get_inventory()
740
799
            # should see the file we poked in...
762
821
        finally:
763
822
            tree.unlock()
764
823
 
765
 
 
766
824
    def test_set_state_from_inventory_mixed_paths(self):
767
825
        tree1 = self.make_branch_and_tree('tree1')
768
826
        self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
809
867
        state = dirstate.DirState.initialize('dirstate')
810
868
        try:
811
869
            # check precondition to be sure the state does change appropriately.
812
 
            self.assertEqual(
813
 
                [(('', '', 'TREE_ROOT'), [('d', '', 0, False,
814
 
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
815
 
                list(state._iter_entries()))
816
 
            state.set_path_id('', 'foobarbaz')
817
 
            expected_rows = [
818
 
                (('', '', 'foobarbaz'), [('d', '', 0, False,
819
 
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
 
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]
820
881
            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'))
821
887
            # should work across save too
822
888
            state.save()
823
889
        finally:
841
907
        state._validate()
842
908
        try:
843
909
            state.set_parent_trees([('parent-revid', rt)], ghosts=[])
844
 
            state.set_path_id('', 'foobarbaz')
 
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
919
            state._validate()
846
920
            # now see that it is what we expected
847
 
            expected_rows = [
848
 
                (('', '', 'TREE_ROOT'),
849
 
                    [('a', '', 0, False, ''),
850
 
                     ('d', '', 0, False, 'parent-revid'),
851
 
                     ]),
852
 
                (('', '', 'foobarbaz'),
853
 
                    [('d', '', 0, False, ''),
854
 
                     ('a', '', 0, False, ''),
855
 
                     ]),
856
 
                ]
 
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]
857
928
            state._validate()
858
929
            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'))
859
940
            # should work across save too
860
941
            state.save()
861
942
        finally:
877
958
        finally:
878
959
            state.unlock()
879
960
 
880
 
 
881
961
    def test_set_parent_trees_no_content(self):
882
962
        # set_parent_trees is a slow but important api to support.
883
963
        tree1 = self.make_branch_and_memory_tree('tree1')
888
968
        finally:
889
969
            tree1.unlock()
890
970
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
891
 
        tree2 = MemoryTree.create_on_branch(branch2)
 
971
        tree2 = memorytree.MemoryTree.create_on_branch(branch2)
892
972
        tree2.lock_write()
893
973
        try:
894
974
            revid2 = tree2.commit('foo')
926
1006
            state.set_parent_trees(
927
1007
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
928
1008
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
929
 
                 ('ghost-rev', tree2.branch.repository.revision_tree(None))),
 
1009
                 ('ghost-rev', tree2.branch.repository.revision_tree(
 
1010
                                   _mod_revision.NULL_REVISION))),
930
1011
                ['ghost-rev'])
931
1012
            self.assertEqual([revid1, revid2, 'ghost-rev'],
932
1013
                             state.get_parent_ids())
936
1017
                [(('', '', root_id), [
937
1018
                  ('d', '', 0, False, dirstate.DirState.NULLSTAT),
938
1019
                  ('d', '', 0, False, revid1),
939
 
                  ('d', '', 0, False, revid2)
 
1020
                  ('d', '', 0, False, revid1)
940
1021
                  ])],
941
1022
                list(state._iter_entries()))
942
1023
        finally:
957
1038
        finally:
958
1039
            tree1.unlock()
959
1040
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
960
 
        tree2 = MemoryTree.create_on_branch(branch2)
 
1041
        tree2 = memorytree.MemoryTree.create_on_branch(branch2)
961
1042
        tree2.lock_write()
962
1043
        try:
963
1044
            tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
970
1051
            (('', '', root_id), [
971
1052
             ('d', '', 0, False, dirstate.DirState.NULLSTAT),
972
1053
             ('d', '', 0, False, revid1.encode('utf8')),
973
 
             ('d', '', 0, False, revid2.encode('utf8'))
 
1054
             ('d', '', 0, False, revid1.encode('utf8'))
974
1055
             ]),
975
1056
            (('', 'a file', 'file-id'), [
976
1057
             ('a', '', 0, False, ''),
1022
1103
            state.unlock()
1023
1104
        state = dirstate.DirState.on_file('dirstate')
1024
1105
        state.lock_read()
1025
 
        try:
1026
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1027
 
        finally:
1028
 
            state.unlock()
 
1106
        self.addCleanup(state.unlock)
 
1107
        self.assertEqual(expected_entries, list(state._iter_entries()))
1029
1108
 
1030
1109
    def test_add_path_to_unversioned_directory(self):
1031
1110
        """Adding a path to an unversioned directory should error.
1036
1115
        """
1037
1116
        self.build_tree(['unversioned/', 'unversioned/a file'])
1038
1117
        state = dirstate.DirState.initialize('dirstate')
1039
 
        try:
1040
 
            self.assertRaises(errors.NotVersionedError, state.add,
1041
 
                'unversioned/a file', 'a-file-id', 'file', None, None)
1042
 
        finally:
1043
 
            state.unlock()
 
1118
        self.addCleanup(state.unlock)
 
1119
        self.assertRaises(errors.NotVersionedError, state.add,
 
1120
                          'unversioned/a file', 'a-file-id', 'file', None, None)
1044
1121
 
1045
1122
    def test_add_directory_to_root_no_parents_all_data(self):
1046
1123
        # The most trivial addition of a dir is when there are no parents and
1066
1143
            state.unlock()
1067
1144
        state = dirstate.DirState.on_file('dirstate')
1068
1145
        state.lock_read()
 
1146
        self.addCleanup(state.unlock)
1069
1147
        state._validate()
1070
 
        try:
1071
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1072
 
        finally:
1073
 
            state.unlock()
 
1148
        self.assertEqual(expected_entries, list(state._iter_entries()))
1074
1149
 
1075
 
    def test_add_symlink_to_root_no_parents_all_data(self):
 
1150
    def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1076
1151
        # The most trivial addition of a symlink when there are no parents and
1077
1152
        # its in the root and all data about the file is supplied
1078
1153
        # bzr doesn't support fake symlinks on windows, yet.
1079
 
        self.requireFeature(SymlinkFeature)
1080
 
        os.symlink('target', 'a link')
1081
 
        stat = os.lstat('a link')
 
1154
        self.requireFeature(tests.SymlinkFeature)
 
1155
        os.symlink(target, link_name)
 
1156
        stat = os.lstat(link_name)
1082
1157
        expected_entries = [
1083
1158
            (('', '', 'TREE_ROOT'), [
1084
1159
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1085
1160
             ]),
1086
 
            (('', 'a link', 'a link id'), [
1087
 
             ('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
 
1161
            (('', link_name.encode('UTF-8'), 'a link id'), [
 
1162
             ('l', target.encode('UTF-8'), stat[6],
 
1163
              False, dirstate.pack_stat(stat)), # current tree
1088
1164
             ]),
1089
1165
            ]
1090
1166
        state = dirstate.DirState.initialize('dirstate')
1091
1167
        try:
1092
 
            state.add('a link', 'a link id', 'symlink', stat, 'target')
 
1168
            state.add(link_name, 'a link id', 'symlink', stat,
 
1169
                      target.encode('UTF-8'))
1093
1170
            # having added it, it should be in the output of iter_entries.
1094
1171
            self.assertEqual(expected_entries, list(state._iter_entries()))
1095
1172
            # saving and reloading should not affect this.
1098
1175
            state.unlock()
1099
1176
        state = dirstate.DirState.on_file('dirstate')
1100
1177
        state.lock_read()
1101
 
        try:
1102
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1103
 
        finally:
1104
 
            state.unlock()
 
1178
        self.addCleanup(state.unlock)
 
1179
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
1180
 
 
1181
    def test_add_symlink_to_root_no_parents_all_data(self):
 
1182
        self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
 
1183
 
 
1184
    def test_add_symlink_unicode_to_root_no_parents_all_data(self):
 
1185
        self.requireFeature(tests.UnicodeFilenameFeature)
 
1186
        self._test_add_symlink_to_root_no_parents_all_data(
 
1187
            u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1105
1188
 
1106
1189
    def test_add_directory_and_child_no_parents_all_data(self):
1107
1190
        # after adding a directory, we should be able to add children to it.
1132
1215
            state.unlock()
1133
1216
        state = dirstate.DirState.on_file('dirstate')
1134
1217
        state.lock_read()
1135
 
        try:
1136
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1137
 
        finally:
1138
 
            state.unlock()
 
1218
        self.addCleanup(state.unlock)
 
1219
        self.assertEqual(expected_entries, list(state._iter_entries()))
1139
1220
 
1140
1221
    def test_add_tree_reference(self):
1141
1222
        # make a dirstate and add a tree reference
1155
1236
            state.unlock()
1156
1237
        # now check we can read it back
1157
1238
        state.lock_read()
 
1239
        self.addCleanup(state.unlock)
1158
1240
        state._validate()
1159
 
        try:
1160
 
            entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1161
 
            self.assertEqual(entry, entry2)
1162
 
            self.assertEqual(entry, expected_entry)
1163
 
            # and lookup by id should work too
1164
 
            entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1165
 
            self.assertEqual(entry, expected_entry)
1166
 
        finally:
1167
 
            state.unlock()
 
1241
        entry2 = state._get_entry(0, 'subdir-id', 'subdir')
 
1242
        self.assertEqual(entry, entry2)
 
1243
        self.assertEqual(entry, expected_entry)
 
1244
        # and lookup by id should work too
 
1245
        entry2 = state._get_entry(0, fileid_utf8='subdir-id')
 
1246
        self.assertEqual(entry, expected_entry)
1168
1247
 
1169
1248
    def test_add_forbidden_names(self):
1170
1249
        state = dirstate.DirState.initialize('dirstate')
1174
1253
        self.assertRaises(errors.BzrError,
1175
1254
            state.add, '..', 'ass-id', 'directory', None, None)
1176
1255
 
 
1256
    def test_set_state_with_rename_b_a_bug_395556(self):
 
1257
        # bug 395556 uncovered a bug where the dirstate ends up with a false
 
1258
        # relocation record - in a tree with no parents there should be no
 
1259
        # absent or relocated records. This then leads to further corruption
 
1260
        # when a commit occurs, as the incorrect relocation gathers an
 
1261
        # incorrect absent in tree 1, and future changes go to pot.
 
1262
        tree1 = self.make_branch_and_tree('tree1')
 
1263
        self.build_tree(['tree1/b'])
 
1264
        tree1.lock_write()
 
1265
        try:
 
1266
            tree1.add(['b'], ['b-id'])
 
1267
            root_id = tree1.get_root_id()
 
1268
            inv = tree1.inventory
 
1269
            state = dirstate.DirState.initialize('dirstate')
 
1270
            try:
 
1271
                # Set the initial state with 'b'
 
1272
                state.set_state_from_inventory(inv)
 
1273
                inv.rename('b-id', root_id, 'a')
 
1274
                # Set the new state with 'a', which currently corrupts.
 
1275
                state.set_state_from_inventory(inv)
 
1276
                expected_result1 = [('', '', root_id, 'd'),
 
1277
                                    ('', 'a', 'b-id', 'f'),
 
1278
                                   ]
 
1279
                values = []
 
1280
                for entry in state._iter_entries():
 
1281
                    values.append(entry[0] + entry[1][0][:1])
 
1282
                self.assertEqual(expected_result1, values)
 
1283
            finally:
 
1284
                state.unlock()
 
1285
        finally:
 
1286
            tree1.unlock()
 
1287
 
1177
1288
 
1178
1289
class TestGetLines(TestCaseWithDirState):
1179
1290
 
1412
1523
        There is one parent tree, which has the same shape with the following variations:
1413
1524
        b/g in the parent is gone.
1414
1525
        b/h in the parent has a different id
1415
 
        b/i is new in the parent 
 
1526
        b/i is new in the parent
1416
1527
        c is renamed to b/j in the parent
1417
1528
 
1418
1529
        :return: The dirstate, still write-locked.
1508
1619
            list(state._iter_child_entries(1, '')))
1509
1620
 
1510
1621
 
1511
 
class TestDirstateSortOrder(TestCaseWithTransport):
 
1622
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1512
1623
    """Test that DirState adds entries in the right order."""
1513
1624
 
1514
1625
    def test_add_sorting(self):
1563
1674
 
1564
1675
        # *really* cheesy way to just get an empty tree
1565
1676
        repo = self.make_repository('repo')
1566
 
        empty_tree = repo.revision_tree(None)
 
1677
        empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1567
1678
        state.set_parent_trees([('null:', empty_tree)], [])
1568
1679
 
1569
1680
        dirblock_names = [d[0] for d in state._dirblocks]
1573
1684
class InstrumentedDirState(dirstate.DirState):
1574
1685
    """An DirState with instrumented sha1 functionality."""
1575
1686
 
1576
 
    def __init__(self, path):
1577
 
        super(InstrumentedDirState, self).__init__(path)
 
1687
    def __init__(self, path, sha1_provider):
 
1688
        super(InstrumentedDirState, self).__init__(path, sha1_provider)
1578
1689
        self._time_offset = 0
1579
1690
        self._log = []
1580
1691
        # member is dynamically set in DirState.__init__ to turn on trace
 
1692
        self._sha1_provider = sha1_provider
1581
1693
        self._sha1_file = self._sha1_file_and_log
1582
1694
 
1583
1695
    def _sha_cutoff_time(self):
1586
1698
 
1587
1699
    def _sha1_file_and_log(self, abspath):
1588
1700
        self._log.append(('sha1', abspath))
1589
 
        return osutils.sha_file_by_name(abspath)
 
1701
        return self._sha1_provider.sha1(abspath)
1590
1702
 
1591
1703
    def _read_link(self, abspath, old_link):
1592
1704
        self._log.append(('read_link', abspath, old_link))
1623
1735
        self.st_ino = ino
1624
1736
        self.st_mode = mode
1625
1737
 
1626
 
 
1627
 
class TestUpdateEntry(TestCaseWithDirState):
1628
 
    """Test the DirState.update_entry functions"""
1629
 
 
1630
 
    def get_state_with_a(self):
1631
 
        """Create a DirState tracking a single object named 'a'"""
1632
 
        state = InstrumentedDirState.initialize('dirstate')
1633
 
        self.addCleanup(state.unlock)
1634
 
        state.add('a', 'a-id', 'file', None, '')
1635
 
        entry = state._get_entry(0, path_utf8='a')
1636
 
        return state, entry
1637
 
 
1638
 
    def test_update_entry(self):
1639
 
        state, entry = self.get_state_with_a()
1640
 
        self.build_tree(['a'])
1641
 
        # Add one where we don't provide the stat or sha already
1642
 
        self.assertEqual(('', 'a', 'a-id'), entry[0])
1643
 
        self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1644
 
                         entry[1])
1645
 
        # Flush the buffers to disk
1646
 
        state.save()
1647
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1648
 
                         state._dirblock_state)
1649
 
 
1650
 
        stat_value = os.lstat('a')
1651
 
        packed_stat = dirstate.pack_stat(stat_value)
1652
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1653
 
                                          stat_value=stat_value)
1654
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1655
 
                         link_or_sha1)
1656
 
 
1657
 
        # The dirblock entry should not cache the file's sha1
1658
 
        self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1659
 
                         entry[1])
1660
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1661
 
                         state._dirblock_state)
1662
 
        mode = stat_value.st_mode
1663
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1664
 
 
1665
 
        state.save()
1666
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1667
 
                         state._dirblock_state)
1668
 
 
1669
 
        # If we do it again right away, we don't know if the file has changed
1670
 
        # so we will re-read the file. Roll the clock back so the file is
1671
 
        # guaranteed to look too new.
1672
 
        state.adjust_time(-10)
1673
 
 
1674
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1675
 
                                          stat_value=stat_value)
1676
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1677
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1678
 
                         ], state._log)
1679
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1680
 
                         link_or_sha1)
1681
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1682
 
                         state._dirblock_state)
1683
 
        self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1684
 
                         entry[1])
1685
 
        state.save()
1686
 
 
1687
 
        # However, if we move the clock forward so the file is considered
1688
 
        # "stable", it should just cache the value.
1689
 
        state.adjust_time(+20)
1690
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1691
 
                                          stat_value=stat_value)
1692
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1693
 
                         link_or_sha1)
1694
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1695
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1696
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1697
 
                         ], state._log)
1698
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1699
 
                         entry[1])
1700
 
 
1701
 
        # Subsequent calls will just return the cached value
1702
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1703
 
                                          stat_value=stat_value)
1704
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1705
 
                         link_or_sha1)
1706
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1707
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1708
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1709
 
                         ], state._log)
1710
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1711
 
                         entry[1])
1712
 
 
1713
 
    def test_update_entry_symlink(self):
1714
 
        """Update entry should read symlinks."""
1715
 
        self.requireFeature(SymlinkFeature)
1716
 
        state, entry = self.get_state_with_a()
1717
 
        state.save()
1718
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1719
 
                         state._dirblock_state)
1720
 
        os.symlink('target', 'a')
1721
 
 
1722
 
        state.adjust_time(-10) # Make the symlink look new
1723
 
        stat_value = os.lstat('a')
1724
 
        packed_stat = dirstate.pack_stat(stat_value)
1725
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1726
 
                                          stat_value=stat_value)
1727
 
        self.assertEqual('target', link_or_sha1)
1728
 
        self.assertEqual([('read_link', 'a', '')], state._log)
1729
 
        # Dirblock is not updated (the link is too new)
1730
 
        self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1731
 
                         entry[1])
1732
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1733
 
                         state._dirblock_state)
1734
 
 
1735
 
        # Because the stat_value looks new, we should re-read the target
1736
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1737
 
                                          stat_value=stat_value)
1738
 
        self.assertEqual('target', link_or_sha1)
1739
 
        self.assertEqual([('read_link', 'a', ''),
1740
 
                          ('read_link', 'a', ''),
1741
 
                         ], state._log)
1742
 
        self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1743
 
                         entry[1])
1744
 
        state.adjust_time(+20) # Skip into the future, all files look old
1745
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1746
 
                                          stat_value=stat_value)
1747
 
        self.assertEqual('target', link_or_sha1)
1748
 
        # We need to re-read the link because only now can we cache it
1749
 
        self.assertEqual([('read_link', 'a', ''),
1750
 
                          ('read_link', 'a', ''),
1751
 
                          ('read_link', 'a', ''),
1752
 
                         ], state._log)
1753
 
        self.assertEqual([('l', 'target', 6, False, packed_stat)],
1754
 
                         entry[1])
1755
 
 
1756
 
        # Another call won't re-read the link
1757
 
        self.assertEqual([('read_link', 'a', ''),
1758
 
                          ('read_link', 'a', ''),
1759
 
                          ('read_link', 'a', ''),
1760
 
                         ], state._log)
1761
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1762
 
                                          stat_value=stat_value)
1763
 
        self.assertEqual('target', link_or_sha1)
1764
 
        self.assertEqual([('l', 'target', 6, False, packed_stat)],
1765
 
                         entry[1])
1766
 
 
1767
 
    def do_update_entry(self, state, entry, abspath):
1768
 
        stat_value = os.lstat(abspath)
1769
 
        return state.update_entry(entry, abspath, stat_value)
1770
 
 
1771
 
    def test_update_entry_dir(self):
1772
 
        state, entry = self.get_state_with_a()
1773
 
        self.build_tree(['a/'])
1774
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1775
 
 
1776
 
    def test_update_entry_dir_unchanged(self):
1777
 
        state, entry = self.get_state_with_a()
1778
 
        self.build_tree(['a/'])
1779
 
        state.adjust_time(+20)
1780
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1781
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1782
 
                         state._dirblock_state)
1783
 
        state.save()
1784
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1785
 
                         state._dirblock_state)
1786
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1787
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1788
 
                         state._dirblock_state)
1789
 
 
1790
 
    def test_update_entry_file_unchanged(self):
1791
 
        state, entry = self.get_state_with_a()
1792
 
        self.build_tree(['a'])
1793
 
        sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1794
 
        state.adjust_time(+20)
1795
 
        self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1796
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1797
 
                         state._dirblock_state)
1798
 
        state.save()
1799
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1800
 
                         state._dirblock_state)
1801
 
        self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1802
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1803
 
                         state._dirblock_state)
1804
 
 
1805
 
    def create_and_test_file(self, state, entry):
1806
 
        """Create a file at 'a' and verify the state finds it.
1807
 
 
1808
 
        The state should already be versioning *something* at 'a'. This makes
1809
 
        sure that state.update_entry recognizes it as a file.
1810
 
        """
1811
 
        self.build_tree(['a'])
1812
 
        stat_value = os.lstat('a')
1813
 
        packed_stat = dirstate.pack_stat(stat_value)
1814
 
 
1815
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1816
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1817
 
                         link_or_sha1)
1818
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1819
 
                         entry[1])
1820
 
        return packed_stat
1821
 
 
1822
 
    def create_and_test_dir(self, state, entry):
1823
 
        """Create a directory at 'a' and verify the state finds it.
1824
 
 
1825
 
        The state should already be versioning *something* at 'a'. This makes
1826
 
        sure that state.update_entry recognizes it as a directory.
1827
 
        """
1828
 
        self.build_tree(['a/'])
1829
 
        stat_value = os.lstat('a')
1830
 
        packed_stat = dirstate.pack_stat(stat_value)
1831
 
 
1832
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1833
 
        self.assertIs(None, link_or_sha1)
1834
 
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1835
 
 
1836
 
        return packed_stat
1837
 
 
1838
 
    def create_and_test_symlink(self, state, entry):
1839
 
        """Create a symlink at 'a' and verify the state finds it.
1840
 
 
1841
 
        The state should already be versioning *something* at 'a'. This makes
1842
 
        sure that state.update_entry recognizes it as a symlink.
1843
 
 
1844
 
        This should not be called if this platform does not have symlink
1845
 
        support.
1846
 
        """
1847
 
        # caller should care about skipping test on platforms without symlinks
1848
 
        os.symlink('path/to/foo', 'a')
1849
 
 
1850
 
        stat_value = os.lstat('a')
1851
 
        packed_stat = dirstate.pack_stat(stat_value)
1852
 
 
1853
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1854
 
        self.assertEqual('path/to/foo', link_or_sha1)
1855
 
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1856
 
                         entry[1])
1857
 
        return packed_stat
1858
 
 
1859
 
    def test_update_file_to_dir(self):
1860
 
        """If a file changes to a directory we return None for the sha.
1861
 
        We also update the inventory record.
1862
 
        """
1863
 
        state, entry = self.get_state_with_a()
1864
 
        # The file sha1 won't be cached unless the file is old
1865
 
        state.adjust_time(+10)
1866
 
        self.create_and_test_file(state, entry)
1867
 
        os.remove('a')
1868
 
        self.create_and_test_dir(state, entry)
1869
 
 
1870
 
    def test_update_file_to_symlink(self):
1871
 
        """File becomes a symlink"""
1872
 
        self.requireFeature(SymlinkFeature)
1873
 
        state, entry = self.get_state_with_a()
1874
 
        # The file sha1 won't be cached unless the file is old
1875
 
        state.adjust_time(+10)
1876
 
        self.create_and_test_file(state, entry)
1877
 
        os.remove('a')
1878
 
        self.create_and_test_symlink(state, entry)
1879
 
 
1880
 
    def test_update_dir_to_file(self):
1881
 
        """Directory becoming a file updates the entry."""
1882
 
        state, entry = self.get_state_with_a()
1883
 
        # The file sha1 won't be cached unless the file is old
1884
 
        state.adjust_time(+10)
1885
 
        self.create_and_test_dir(state, entry)
1886
 
        os.rmdir('a')
1887
 
        self.create_and_test_file(state, entry)
1888
 
 
1889
 
    def test_update_dir_to_symlink(self):
1890
 
        """Directory becomes a symlink"""
1891
 
        self.requireFeature(SymlinkFeature)
1892
 
        state, entry = self.get_state_with_a()
1893
 
        # The symlink target won't be cached if it isn't old
1894
 
        state.adjust_time(+10)
1895
 
        self.create_and_test_dir(state, entry)
1896
 
        os.rmdir('a')
1897
 
        self.create_and_test_symlink(state, entry)
1898
 
 
1899
 
    def test_update_symlink_to_file(self):
1900
 
        """Symlink becomes a file"""
1901
 
        self.requireFeature(SymlinkFeature)
1902
 
        state, entry = self.get_state_with_a()
1903
 
        # The symlink and file info won't be cached unless old
1904
 
        state.adjust_time(+10)
1905
 
        self.create_and_test_symlink(state, entry)
1906
 
        os.remove('a')
1907
 
        self.create_and_test_file(state, entry)
1908
 
 
1909
 
    def test_update_symlink_to_dir(self):
1910
 
        """Symlink becomes a directory"""
1911
 
        self.requireFeature(SymlinkFeature)
1912
 
        state, entry = self.get_state_with_a()
1913
 
        # The symlink target won't be cached if it isn't old
1914
 
        state.adjust_time(+10)
1915
 
        self.create_and_test_symlink(state, entry)
1916
 
        os.remove('a')
1917
 
        self.create_and_test_dir(state, entry)
1918
 
 
1919
 
    def test__is_executable_win32(self):
1920
 
        state, entry = self.get_state_with_a()
1921
 
        self.build_tree(['a'])
1922
 
 
1923
 
        # Make sure we are using the win32 implementation of _is_executable
1924
 
        state._is_executable = state._is_executable_win32
1925
 
 
1926
 
        # The file on disk is not executable, but we are marking it as though
1927
 
        # it is. With _is_executable_win32 we ignore what is on disk.
1928
 
        entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1929
 
 
1930
 
        stat_value = os.lstat('a')
1931
 
        packed_stat = dirstate.pack_stat(stat_value)
1932
 
 
1933
 
        state.adjust_time(-10) # Make sure everything is new
1934
 
        state.update_entry(entry, abspath='a', stat_value=stat_value)
1935
 
 
1936
 
        # The row is updated, but the executable bit stays set.
1937
 
        self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1938
 
                         entry[1])
1939
 
 
1940
 
        # Make the disk object look old enough to cache
1941
 
        state.adjust_time(+20)
1942
 
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1943
 
        state.update_entry(entry, abspath='a', stat_value=stat_value)
1944
 
        self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1945
 
 
1946
 
 
1947
 
class TestPackStat(TestCaseWithTransport):
 
1738
    @staticmethod
 
1739
    def from_stat(st):
 
1740
        return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
 
1741
            st.st_ino, st.st_mode)
 
1742
 
 
1743
 
 
1744
class TestPackStat(tests.TestCaseWithTransport):
1948
1745
 
1949
1746
    def assertPackStat(self, expected, stat_value):
1950
1747
        """Check the packed and serialized form of a stat value."""
2015
1812
        # the end it would still be fairly arbitrary, and we don't want the
2016
1813
        # extra overhead if we can avoid it. So sort everything to make sure
2017
1814
        # equality is true
2018
 
        assert len(map_keys) == len(paths)
 
1815
        self.assertEqual(len(map_keys), len(paths))
2019
1816
        expected = {}
2020
1817
        for path, keys in zip(paths, map_keys):
2021
1818
            if keys is None:
2040
1837
        :param paths: A list of directories
2041
1838
        """
2042
1839
        result = state._bisect_dirblocks(paths)
2043
 
        assert len(map_keys) == len(paths)
2044
 
 
 
1840
        self.assertEqual(len(map_keys), len(paths))
2045
1841
        expected = {}
2046
1842
        for path, keys in zip(paths, map_keys):
2047
1843
            if keys is None:
2476
2272
        state._discard_merge_parents()
2477
2273
        state._validate()
2478
2274
        self.assertEqual(exp_dirblocks, state._dirblocks)
 
2275
 
 
2276
 
 
2277
class Test_InvEntryToDetails(tests.TestCase):
 
2278
 
 
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
 
2283
        # finished
 
2284
        (minikind, fingerprint, size, executable, tree_data) = details
 
2285
        self.assertIsInstance(minikind, str)
 
2286
        self.assertIsInstance(fingerprint, str)
 
2287
        self.assertIsInstance(tree_data, str)
 
2288
 
 
2289
    def test_unicode_symlink(self):
 
2290
        inv_entry = inventory.InventoryLink('link-file-id',
 
2291
                                            u'nam\N{Euro Sign}e',
 
2292
                                            'link-parent-id')
 
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)
 
2298
 
 
2299
 
 
2300
class TestSHA1Provider(tests.TestCaseInTempDir):
 
2301
 
 
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")
 
2306
 
 
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'))
 
2313
 
 
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)