~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

(jelmer) Support upgrading between the 2a and development-colo formats.
 (Jelmer Vernooij)

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007 Canonical Ltd
 
1
# Copyright (C) 2006-2011 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
 
import bisect
20
19
import os
21
 
import time
 
20
import tempfile
22
21
 
23
22
from bzrlib import (
 
23
    bzrdir,
24
24
    dirstate,
25
25
    errors,
 
26
    inventory,
 
27
    memorytree,
26
28
    osutils,
 
29
    revision as _mod_revision,
 
30
    revisiontree,
 
31
    tests,
 
32
    workingtree_4,
27
33
    )
28
 
from bzrlib.memorytree import MemoryTree
 
34
from bzrlib.transport import memory
29
35
from bzrlib.tests import (
30
 
        SymlinkFeature,
31
 
        TestCase,
32
 
        TestCaseWithTransport,
33
 
        )
 
36
    features,
 
37
    test_osutils,
 
38
    )
 
39
from bzrlib.tests.scenarios import load_tests_apply_scenarios
34
40
 
35
41
 
36
42
# TODO:
46
52
# set_path_id  setting id when state is in memory modified
47
53
 
48
54
 
49
 
class TestCaseWithDirState(TestCaseWithTransport):
 
55
load_tests = load_tests_apply_scenarios
 
56
 
 
57
 
 
58
class TestCaseWithDirState(tests.TestCaseWithTransport):
50
59
    """Helper functions for creating DirState objects with various content."""
51
60
 
 
61
    scenarios = test_osutils.dir_reader_scenarios()
 
62
 
 
63
    # Set by load_tests
 
64
    _dir_reader_class = None
 
65
    _native_to_unicode = None # Not used yet
 
66
 
 
67
    def setUp(self):
 
68
        tests.TestCaseWithTransport.setUp(self)
 
69
 
 
70
        self.overrideAttr(osutils,
 
71
                          '_selected_dir_reader', self._dir_reader_class())
 
72
 
52
73
    def create_empty_dirstate(self):
53
74
        """Return a locked but empty dirstate"""
54
75
        state = dirstate.DirState.initialize('dirstate')
162
183
        """
163
184
        # The state should already be write locked, since we just had to do
164
185
        # some operation to get here.
165
 
        assert state._lock_token is not None
 
186
        self.assertTrue(state._lock_token is not None)
166
187
        try:
167
188
            self.assertEqual(expected_result[0],  state.get_parent_ids())
168
189
            # there should be no ghosts in this tree.
395
416
            (('', '', tree.get_root_id()), # common details
396
417
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
397
418
              ('d', '', 0, False, rev_id), # first parent details
398
 
              ('d', '', 0, False, rev_id2), # second parent details
 
419
              ('d', '', 0, False, rev_id), # second parent details
399
420
             ])])
400
421
        state = dirstate.DirState.from_tree(tree, 'dirstate')
401
422
        self.check_state_with_reopen(expected_result, state)
476
497
            (('', '', tree.get_root_id()), # common details
477
498
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
478
499
              ('d', '', 0, False, rev_id), # first parent details
479
 
              ('d', '', 0, False, rev_id2), # second parent details
 
500
              ('d', '', 0, False, rev_id), # second parent details
480
501
             ]),
481
502
            (('', 'a file', 'a-file-id'), # common
482
503
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
514
535
 
515
536
class TestDirStateOnFile(TestCaseWithDirState):
516
537
 
 
538
    def create_updated_dirstate(self):
 
539
        self.build_tree(['a-file'])
 
540
        tree = self.make_branch_and_tree('.')
 
541
        tree.add(['a-file'], ['a-id'])
 
542
        tree.commit('add a-file')
 
543
        # Save and unlock the state, re-open it in readonly mode
 
544
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
545
        state.save()
 
546
        state.unlock()
 
547
        state = dirstate.DirState.on_file('dirstate')
 
548
        state.lock_read()
 
549
        return state
 
550
 
517
551
    def test_construct_with_path(self):
518
552
        tree = self.make_branch_and_tree('tree')
519
553
        state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
548
582
            state.unlock()
549
583
 
550
584
    def test_can_save_in_read_lock(self):
551
 
        self.build_tree(['a-file'])
552
 
        state = dirstate.DirState.initialize('dirstate')
553
 
        try:
554
 
            # No stat and no sha1 sum.
555
 
            state.add('a-file', 'a-file-id', 'file', None, '')
556
 
            state.save()
557
 
        finally:
558
 
            state.unlock()
559
 
 
560
 
        # Now open in readonly mode
561
 
        state = dirstate.DirState.on_file('dirstate')
562
 
        state.lock_read()
 
585
        state = self.create_updated_dirstate()
563
586
        try:
564
587
            entry = state._get_entry(0, path_utf8='a-file')
565
 
            # The current sha1 sum should be empty
566
 
            self.assertEqual('', entry[1][0][1])
 
588
            # The current size should be 0 (default)
 
589
            self.assertEqual(0, entry[1][0][2])
567
590
            # We should have a real entry.
568
591
            self.assertNotEqual((None, None), entry)
569
 
            # Make sure everything is old enough
 
592
            # Set the cutoff-time into the future, so things look cacheable
570
593
            state._sha_cutoff_time()
571
 
            state._cutoff_time += 10
572
 
            sha1sum = state.update_entry(entry, 'a-file', os.lstat('a-file'))
573
 
            # We should have gotten a real sha1
 
594
            state._cutoff_time += 10.0
 
595
            st = os.lstat('a-file')
 
596
            sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
 
597
            # We updated the current sha1sum because the file is cacheable
574
598
            self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
575
599
                             sha1sum)
576
600
 
577
601
            # The dirblock has been updated
578
 
            self.assertEqual(sha1sum, entry[1][0][1])
579
 
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
602
            self.assertEqual(st.st_size, entry[1][0][2])
 
603
            self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
580
604
                             state._dirblock_state)
581
605
 
582
606
            del entry
591
615
        state.lock_read()
592
616
        try:
593
617
            entry = state._get_entry(0, path_utf8='a-file')
594
 
            self.assertEqual(sha1sum, entry[1][0][1])
 
618
            self.assertEqual(st.st_size, entry[1][0][2])
595
619
        finally:
596
620
            state.unlock()
597
621
 
598
622
    def test_save_fails_quietly_if_locked(self):
599
623
        """If dirstate is locked, save will fail without complaining."""
600
 
        self.build_tree(['a-file'])
601
 
        state = dirstate.DirState.initialize('dirstate')
602
 
        try:
603
 
            # No stat and no sha1 sum.
604
 
            state.add('a-file', 'a-file-id', 'file', None, '')
605
 
            state.save()
606
 
        finally:
607
 
            state.unlock()
608
 
 
609
 
        state = dirstate.DirState.on_file('dirstate')
610
 
        state.lock_read()
 
624
        state = self.create_updated_dirstate()
611
625
        try:
612
626
            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
 
627
            # No cached sha1 yet.
 
628
            self.assertEqual('', entry[1][0][1])
 
629
            # Set the cutoff-time into the future, so things look cacheable
 
630
            state._sha_cutoff_time()
 
631
            state._cutoff_time += 10.0
 
632
            st = os.lstat('a-file')
 
633
            sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
615
634
            self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
616
635
                             sha1sum)
617
 
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
636
            self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
618
637
                             state._dirblock_state)
619
638
 
620
639
            # Now, before we try to save, grab another dirstate, and take out a
636
655
                state2.unlock()
637
656
        finally:
638
657
            state.unlock()
639
 
        
 
658
 
640
659
        # The file on disk should not be modified.
641
660
        state = dirstate.DirState.on_file('dirstate')
642
661
        state.lock_read()
710
729
 
711
730
class TestDirStateManipulations(TestCaseWithDirState):
712
731
 
 
732
    def make_minimal_tree(self):
 
733
        tree1 = self.make_branch_and_memory_tree('tree1')
 
734
        tree1.lock_write()
 
735
        self.addCleanup(tree1.unlock)
 
736
        tree1.add('')
 
737
        revid1 = tree1.commit('foo')
 
738
        return tree1, revid1
 
739
 
 
740
    def test_update_minimal_updates_id_index(self):
 
741
        state = self.create_dirstate_with_root_and_subdir()
 
742
        self.addCleanup(state.unlock)
 
743
        id_index = state._get_id_index()
 
744
        self.assertEqual(['a-root-value', 'subdir-id'], sorted(id_index))
 
745
        state.add('file-name', 'file-id', 'file', None, '')
 
746
        self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
 
747
                         sorted(id_index))
 
748
        state.update_minimal(('', 'new-name', 'file-id'), 'f',
 
749
                             path_utf8='new-name')
 
750
        self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
 
751
                         sorted(id_index))
 
752
        self.assertEqual([('', 'new-name', 'file-id')],
 
753
                         sorted(id_index['file-id']))
 
754
        state._validate()
 
755
 
713
756
    def test_set_state_from_inventory_no_content_no_parents(self):
714
757
        # setting the current inventory is a slow but important api to support.
715
 
        tree1 = self.make_branch_and_memory_tree('tree1')
716
 
        tree1.lock_write()
717
 
        try:
718
 
            tree1.add('')
719
 
            revid1 = tree1.commit('foo').encode('utf8')
720
 
            root_id = tree1.get_root_id()
721
 
            inv = tree1.inventory
722
 
        finally:
723
 
            tree1.unlock()
 
758
        tree1, revid1 = self.make_minimal_tree()
 
759
        inv = tree1.inventory
 
760
        root_id = inv.path2id('')
724
761
        expected_result = [], [
725
762
            (('', '', root_id), [
726
763
             ('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
738
775
            # This will unlock it
739
776
            self.check_state_with_reopen(expected_result, state)
740
777
 
 
778
    def test_set_state_from_scratch_no_parents(self):
 
779
        tree1, revid1 = self.make_minimal_tree()
 
780
        inv = tree1.inventory
 
781
        root_id = inv.path2id('')
 
782
        expected_result = [], [
 
783
            (('', '', root_id), [
 
784
             ('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
 
785
        state = dirstate.DirState.initialize('dirstate')
 
786
        try:
 
787
            state.set_state_from_scratch(inv, [], [])
 
788
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
789
                             state._header_state)
 
790
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
791
                             state._dirblock_state)
 
792
        except:
 
793
            state.unlock()
 
794
            raise
 
795
        else:
 
796
            # This will unlock it
 
797
            self.check_state_with_reopen(expected_result, state)
 
798
 
 
799
    def test_set_state_from_scratch_identical_parent(self):
 
800
        tree1, revid1 = self.make_minimal_tree()
 
801
        inv = tree1.inventory
 
802
        root_id = inv.path2id('')
 
803
        rev_tree1 = tree1.branch.repository.revision_tree(revid1)
 
804
        d_entry = ('d', '', 0, False, dirstate.DirState.NULLSTAT)
 
805
        parent_entry = ('d', '', 0, False, revid1)
 
806
        expected_result = [revid1], [
 
807
            (('', '', root_id), [d_entry, parent_entry])]
 
808
        state = dirstate.DirState.initialize('dirstate')
 
809
        try:
 
810
            state.set_state_from_scratch(inv, [(revid1, rev_tree1)], [])
 
811
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
812
                             state._header_state)
 
813
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
814
                             state._dirblock_state)
 
815
        except:
 
816
            state.unlock()
 
817
            raise
 
818
        else:
 
819
            # This will unlock it
 
820
            self.check_state_with_reopen(expected_result, state)
 
821
 
741
822
    def test_set_state_from_inventory_preserves_hashcache(self):
742
823
        # https://bugs.launchpad.net/bzr/+bug/146176
743
824
        # set_state_from_inventory should preserve the stat and hash value for
744
825
        # workingtree files that are not changed by the inventory.
745
 
       
 
826
 
746
827
        tree = self.make_branch_and_tree('.')
747
828
        # depends on the default format using dirstate...
748
829
        tree.lock_write()
749
830
        try:
750
 
            # make a dirstate with some valid hashcache data 
 
831
            # make a dirstate with some valid hashcache data
751
832
            # file on disk, but that's not needed for this test
752
833
            foo_contents = 'contents of foo'
753
834
            self.build_tree_contents([('foo', foo_contents)])
773
854
                (('', 'foo', 'foo-id',),
774
855
                 [('f', foo_sha, foo_size, False, foo_packed)]),
775
856
                tree._dirstate._get_entry(0, 'foo-id'))
776
 
           
 
857
 
777
858
            # extract the inventory, and add something to it
778
859
            inv = tree._get_inventory()
779
860
            # should see the file we poked in...
801
882
        finally:
802
883
            tree.unlock()
803
884
 
804
 
 
805
885
    def test_set_state_from_inventory_mixed_paths(self):
806
886
        tree1 = self.make_branch_and_tree('tree1')
807
887
        self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
848
928
        state = dirstate.DirState.initialize('dirstate')
849
929
        try:
850
930
            # check precondition to be sure the state does change appropriately.
851
 
            self.assertEqual(
852
 
                [(('', '', 'TREE_ROOT'), [('d', '', 0, False,
853
 
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
854
 
                list(state._iter_entries()))
855
 
            state.set_path_id('', 'foobarbaz')
856
 
            expected_rows = [
857
 
                (('', '', 'foobarbaz'), [('d', '', 0, False,
858
 
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
 
931
            root_entry = (('', '', 'TREE_ROOT'), [('d', '', 0, False, 'x'*32)])
 
932
            self.assertEqual([root_entry], list(state._iter_entries()))
 
933
            self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
 
934
            self.assertEqual(root_entry,
 
935
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
 
936
            self.assertEqual((None, None),
 
937
                             state._get_entry(0, fileid_utf8='second-root-id'))
 
938
            state.set_path_id('', 'second-root-id')
 
939
            new_root_entry = (('', '', 'second-root-id'),
 
940
                              [('d', '', 0, False, 'x'*32)])
 
941
            expected_rows = [new_root_entry]
859
942
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
943
            self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
 
944
            self.assertEqual(new_root_entry, 
 
945
                             state._get_entry(0, fileid_utf8='second-root-id'))
 
946
            self.assertEqual((None, None),
 
947
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
860
948
            # should work across save too
861
949
            state.save()
862
950
        finally:
880
968
        state._validate()
881
969
        try:
882
970
            state.set_parent_trees([('parent-revid', rt)], ghosts=[])
883
 
            state.set_path_id('', 'foobarbaz')
 
971
            root_entry = (('', '', 'TREE_ROOT'),
 
972
                          [('d', '', 0, False, 'x'*32),
 
973
                           ('d', '', 0, False, 'parent-revid')])
 
974
            self.assertEqual(root_entry, state._get_entry(0, path_utf8=''))
 
975
            self.assertEqual(root_entry,
 
976
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
 
977
            self.assertEqual((None, None),
 
978
                             state._get_entry(0, fileid_utf8='Asecond-root-id'))
 
979
            state.set_path_id('', 'Asecond-root-id')
884
980
            state._validate()
885
981
            # now see that it is what we expected
886
 
            expected_rows = [
887
 
                (('', '', 'TREE_ROOT'),
888
 
                    [('a', '', 0, False, ''),
889
 
                     ('d', '', 0, False, 'parent-revid'),
890
 
                     ]),
891
 
                (('', '', 'foobarbaz'),
892
 
                    [('d', '', 0, False, ''),
893
 
                     ('a', '', 0, False, ''),
894
 
                     ]),
895
 
                ]
 
982
            old_root_entry = (('', '', 'TREE_ROOT'),
 
983
                              [('a', '', 0, False, ''),
 
984
                               ('d', '', 0, False, 'parent-revid')])
 
985
            new_root_entry = (('', '', 'Asecond-root-id'),
 
986
                              [('d', '', 0, False, ''),
 
987
                               ('a', '', 0, False, '')])
 
988
            expected_rows = [new_root_entry, old_root_entry]
896
989
            state._validate()
897
990
            self.assertEqual(expected_rows, list(state._iter_entries()))
 
991
            self.assertEqual(new_root_entry, state._get_entry(0, path_utf8=''))
 
992
            self.assertEqual(old_root_entry, state._get_entry(1, path_utf8=''))
 
993
            self.assertEqual((None, None),
 
994
                             state._get_entry(0, fileid_utf8='TREE_ROOT'))
 
995
            self.assertEqual(old_root_entry,
 
996
                             state._get_entry(1, fileid_utf8='TREE_ROOT'))
 
997
            self.assertEqual(new_root_entry,
 
998
                             state._get_entry(0, fileid_utf8='Asecond-root-id'))
 
999
            self.assertEqual((None, None),
 
1000
                             state._get_entry(1, fileid_utf8='Asecond-root-id'))
898
1001
            # should work across save too
899
1002
            state.save()
900
1003
        finally:
916
1019
        finally:
917
1020
            state.unlock()
918
1021
 
919
 
 
920
1022
    def test_set_parent_trees_no_content(self):
921
1023
        # set_parent_trees is a slow but important api to support.
922
1024
        tree1 = self.make_branch_and_memory_tree('tree1')
927
1029
        finally:
928
1030
            tree1.unlock()
929
1031
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
930
 
        tree2 = MemoryTree.create_on_branch(branch2)
 
1032
        tree2 = memorytree.MemoryTree.create_on_branch(branch2)
931
1033
        tree2.lock_write()
932
1034
        try:
933
1035
            revid2 = tree2.commit('foo')
965
1067
            state.set_parent_trees(
966
1068
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
967
1069
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
968
 
                 ('ghost-rev', tree2.branch.repository.revision_tree(None))),
 
1070
                 ('ghost-rev', tree2.branch.repository.revision_tree(
 
1071
                                   _mod_revision.NULL_REVISION))),
969
1072
                ['ghost-rev'])
970
1073
            self.assertEqual([revid1, revid2, 'ghost-rev'],
971
1074
                             state.get_parent_ids())
975
1078
                [(('', '', root_id), [
976
1079
                  ('d', '', 0, False, dirstate.DirState.NULLSTAT),
977
1080
                  ('d', '', 0, False, revid1),
978
 
                  ('d', '', 0, False, revid2)
 
1081
                  ('d', '', 0, False, revid1)
979
1082
                  ])],
980
1083
                list(state._iter_entries()))
981
1084
        finally:
996
1099
        finally:
997
1100
            tree1.unlock()
998
1101
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
999
 
        tree2 = MemoryTree.create_on_branch(branch2)
 
1102
        tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1000
1103
        tree2.lock_write()
1001
1104
        try:
1002
1105
            tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
1009
1112
            (('', '', root_id), [
1010
1113
             ('d', '', 0, False, dirstate.DirState.NULLSTAT),
1011
1114
             ('d', '', 0, False, revid1.encode('utf8')),
1012
 
             ('d', '', 0, False, revid2.encode('utf8'))
 
1115
             ('d', '', 0, False, revid1.encode('utf8'))
1013
1116
             ]),
1014
1117
            (('', 'a file', 'file-id'), [
1015
1118
             ('a', '', 0, False, ''),
1061
1164
            state.unlock()
1062
1165
        state = dirstate.DirState.on_file('dirstate')
1063
1166
        state.lock_read()
1064
 
        try:
1065
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1066
 
        finally:
1067
 
            state.unlock()
 
1167
        self.addCleanup(state.unlock)
 
1168
        self.assertEqual(expected_entries, list(state._iter_entries()))
1068
1169
 
1069
1170
    def test_add_path_to_unversioned_directory(self):
1070
1171
        """Adding a path to an unversioned directory should error.
1075
1176
        """
1076
1177
        self.build_tree(['unversioned/', 'unversioned/a file'])
1077
1178
        state = dirstate.DirState.initialize('dirstate')
1078
 
        try:
1079
 
            self.assertRaises(errors.NotVersionedError, state.add,
1080
 
                'unversioned/a file', 'a-file-id', 'file', None, None)
1081
 
        finally:
1082
 
            state.unlock()
 
1179
        self.addCleanup(state.unlock)
 
1180
        self.assertRaises(errors.NotVersionedError, state.add,
 
1181
                          'unversioned/a file', 'a-file-id', 'file', None, None)
1083
1182
 
1084
1183
    def test_add_directory_to_root_no_parents_all_data(self):
1085
1184
        # The most trivial addition of a dir is when there are no parents and
1105
1204
            state.unlock()
1106
1205
        state = dirstate.DirState.on_file('dirstate')
1107
1206
        state.lock_read()
 
1207
        self.addCleanup(state.unlock)
1108
1208
        state._validate()
1109
 
        try:
1110
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1111
 
        finally:
1112
 
            state.unlock()
 
1209
        self.assertEqual(expected_entries, list(state._iter_entries()))
1113
1210
 
1114
 
    def test_add_symlink_to_root_no_parents_all_data(self):
 
1211
    def _test_add_symlink_to_root_no_parents_all_data(self, link_name, target):
1115
1212
        # The most trivial addition of a symlink when there are no parents and
1116
1213
        # its in the root and all data about the file is supplied
1117
1214
        # bzr doesn't support fake symlinks on windows, yet.
1118
 
        self.requireFeature(SymlinkFeature)
1119
 
        os.symlink('target', 'a link')
1120
 
        stat = os.lstat('a link')
 
1215
        self.requireFeature(features.SymlinkFeature)
 
1216
        os.symlink(target, link_name)
 
1217
        stat = os.lstat(link_name)
1121
1218
        expected_entries = [
1122
1219
            (('', '', 'TREE_ROOT'), [
1123
1220
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1124
1221
             ]),
1125
 
            (('', 'a link', 'a link id'), [
1126
 
             ('l', 'target', 6, False, dirstate.pack_stat(stat)), # current tree
 
1222
            (('', link_name.encode('UTF-8'), 'a link id'), [
 
1223
             ('l', target.encode('UTF-8'), stat[6],
 
1224
              False, dirstate.pack_stat(stat)), # current tree
1127
1225
             ]),
1128
1226
            ]
1129
1227
        state = dirstate.DirState.initialize('dirstate')
1130
1228
        try:
1131
 
            state.add('a link', 'a link id', 'symlink', stat, 'target')
 
1229
            state.add(link_name, 'a link id', 'symlink', stat,
 
1230
                      target.encode('UTF-8'))
1132
1231
            # having added it, it should be in the output of iter_entries.
1133
1232
            self.assertEqual(expected_entries, list(state._iter_entries()))
1134
1233
            # saving and reloading should not affect this.
1137
1236
            state.unlock()
1138
1237
        state = dirstate.DirState.on_file('dirstate')
1139
1238
        state.lock_read()
1140
 
        try:
1141
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1142
 
        finally:
1143
 
            state.unlock()
 
1239
        self.addCleanup(state.unlock)
 
1240
        self.assertEqual(expected_entries, list(state._iter_entries()))
 
1241
 
 
1242
    def test_add_symlink_to_root_no_parents_all_data(self):
 
1243
        self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
 
1244
 
 
1245
    def test_add_symlink_unicode_to_root_no_parents_all_data(self):
 
1246
        self.requireFeature(features.UnicodeFilenameFeature)
 
1247
        self._test_add_symlink_to_root_no_parents_all_data(
 
1248
            u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1144
1249
 
1145
1250
    def test_add_directory_and_child_no_parents_all_data(self):
1146
1251
        # after adding a directory, we should be able to add children to it.
1171
1276
            state.unlock()
1172
1277
        state = dirstate.DirState.on_file('dirstate')
1173
1278
        state.lock_read()
1174
 
        try:
1175
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1176
 
        finally:
1177
 
            state.unlock()
 
1279
        self.addCleanup(state.unlock)
 
1280
        self.assertEqual(expected_entries, list(state._iter_entries()))
1178
1281
 
1179
1282
    def test_add_tree_reference(self):
1180
1283
        # make a dirstate and add a tree reference
1194
1297
            state.unlock()
1195
1298
        # now check we can read it back
1196
1299
        state.lock_read()
 
1300
        self.addCleanup(state.unlock)
1197
1301
        state._validate()
1198
 
        try:
1199
 
            entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1200
 
            self.assertEqual(entry, entry2)
1201
 
            self.assertEqual(entry, expected_entry)
1202
 
            # and lookup by id should work too
1203
 
            entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1204
 
            self.assertEqual(entry, expected_entry)
1205
 
        finally:
1206
 
            state.unlock()
 
1302
        entry2 = state._get_entry(0, 'subdir-id', 'subdir')
 
1303
        self.assertEqual(entry, entry2)
 
1304
        self.assertEqual(entry, expected_entry)
 
1305
        # and lookup by id should work too
 
1306
        entry2 = state._get_entry(0, fileid_utf8='subdir-id')
 
1307
        self.assertEqual(entry, expected_entry)
1207
1308
 
1208
1309
    def test_add_forbidden_names(self):
1209
1310
        state = dirstate.DirState.initialize('dirstate')
1213
1314
        self.assertRaises(errors.BzrError,
1214
1315
            state.add, '..', 'ass-id', 'directory', None, None)
1215
1316
 
 
1317
    def test_set_state_with_rename_b_a_bug_395556(self):
 
1318
        # bug 395556 uncovered a bug where the dirstate ends up with a false
 
1319
        # relocation record - in a tree with no parents there should be no
 
1320
        # absent or relocated records. This then leads to further corruption
 
1321
        # when a commit occurs, as the incorrect relocation gathers an
 
1322
        # incorrect absent in tree 1, and future changes go to pot.
 
1323
        tree1 = self.make_branch_and_tree('tree1')
 
1324
        self.build_tree(['tree1/b'])
 
1325
        tree1.lock_write()
 
1326
        try:
 
1327
            tree1.add(['b'], ['b-id'])
 
1328
            root_id = tree1.get_root_id()
 
1329
            inv = tree1.inventory
 
1330
            state = dirstate.DirState.initialize('dirstate')
 
1331
            try:
 
1332
                # Set the initial state with 'b'
 
1333
                state.set_state_from_inventory(inv)
 
1334
                inv.rename('b-id', root_id, 'a')
 
1335
                # Set the new state with 'a', which currently corrupts.
 
1336
                state.set_state_from_inventory(inv)
 
1337
                expected_result1 = [('', '', root_id, 'd'),
 
1338
                                    ('', 'a', 'b-id', 'f'),
 
1339
                                   ]
 
1340
                values = []
 
1341
                for entry in state._iter_entries():
 
1342
                    values.append(entry[0] + entry[1][0][:1])
 
1343
                self.assertEqual(expected_result1, values)
 
1344
            finally:
 
1345
                state.unlock()
 
1346
        finally:
 
1347
            tree1.unlock()
 
1348
 
 
1349
 
 
1350
class TestDirStateHashUpdates(TestCaseWithDirState):
 
1351
 
 
1352
    def do_update_entry(self, state, path):
 
1353
        entry = state._get_entry(0, path_utf8=path)
 
1354
        stat = os.lstat(path)
 
1355
        return dirstate.update_entry(state, entry, os.path.abspath(path), stat)
 
1356
 
 
1357
    def _read_state_content(self, state):
 
1358
        """Read the content of the dirstate file.
 
1359
 
 
1360
        On Windows when one process locks a file, you can't even open() the
 
1361
        file in another process (to read it). So we go directly to
 
1362
        state._state_file. This should always be the exact disk representation,
 
1363
        so it is reasonable to do so.
 
1364
        DirState also always seeks before reading, so it doesn't matter if we
 
1365
        bump the file pointer.
 
1366
        """
 
1367
        state._state_file.seek(0)
 
1368
        return state._state_file.read()
 
1369
 
 
1370
    def test_worth_saving_limit_avoids_writing(self):
 
1371
        tree = self.make_branch_and_tree('.')
 
1372
        self.build_tree(['c', 'd'])
 
1373
        tree.lock_write()
 
1374
        tree.add(['c', 'd'], ['c-id', 'd-id'])
 
1375
        tree.commit('add c and d')
 
1376
        state = InstrumentedDirState.on_file(tree.current_dirstate()._filename,
 
1377
                                             worth_saving_limit=2)
 
1378
        tree.unlock()
 
1379
        state.lock_write()
 
1380
        self.addCleanup(state.unlock)
 
1381
        state._read_dirblocks_if_needed()
 
1382
        state.adjust_time(+20) # Allow things to be cached
 
1383
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
1384
                         state._dirblock_state)
 
1385
        content = self._read_state_content(state)
 
1386
        self.do_update_entry(state, 'c')
 
1387
        self.assertEqual(1, len(state._known_hash_changes))
 
1388
        self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
 
1389
                         state._dirblock_state)
 
1390
        state.save()
 
1391
        # It should not have set the state to IN_MEMORY_UNMODIFIED because the
 
1392
        # hash values haven't been written out.
 
1393
        self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
 
1394
                         state._dirblock_state)
 
1395
        self.assertEqual(content, self._read_state_content(state))
 
1396
        self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
 
1397
                         state._dirblock_state)
 
1398
        self.do_update_entry(state, 'd')
 
1399
        self.assertEqual(2, len(state._known_hash_changes))
 
1400
        state.save()
 
1401
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
1402
                         state._dirblock_state)
 
1403
        self.assertEqual(0, len(state._known_hash_changes))
 
1404
 
1216
1405
 
1217
1406
class TestGetLines(TestCaseWithDirState):
1218
1407
 
1451
1640
        There is one parent tree, which has the same shape with the following variations:
1452
1641
        b/g in the parent is gone.
1453
1642
        b/h in the parent has a different id
1454
 
        b/i is new in the parent 
 
1643
        b/i is new in the parent
1455
1644
        c is renamed to b/j in the parent
1456
1645
 
1457
1646
        :return: The dirstate, still write-locked.
1547
1736
            list(state._iter_child_entries(1, '')))
1548
1737
 
1549
1738
 
1550
 
class TestDirstateSortOrder(TestCaseWithTransport):
 
1739
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1551
1740
    """Test that DirState adds entries in the right order."""
1552
1741
 
1553
1742
    def test_add_sorting(self):
1602
1791
 
1603
1792
        # *really* cheesy way to just get an empty tree
1604
1793
        repo = self.make_repository('repo')
1605
 
        empty_tree = repo.revision_tree(None)
 
1794
        empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1606
1795
        state.set_parent_trees([('null:', empty_tree)], [])
1607
1796
 
1608
1797
        dirblock_names = [d[0] for d in state._dirblocks]
1612
1801
class InstrumentedDirState(dirstate.DirState):
1613
1802
    """An DirState with instrumented sha1 functionality."""
1614
1803
 
1615
 
    def __init__(self, path):
1616
 
        super(InstrumentedDirState, self).__init__(path)
 
1804
    def __init__(self, path, sha1_provider, worth_saving_limit=0):
 
1805
        super(InstrumentedDirState, self).__init__(path, sha1_provider,
 
1806
            worth_saving_limit=worth_saving_limit)
1617
1807
        self._time_offset = 0
1618
1808
        self._log = []
1619
1809
        # member is dynamically set in DirState.__init__ to turn on trace
 
1810
        self._sha1_provider = sha1_provider
1620
1811
        self._sha1_file = self._sha1_file_and_log
1621
1812
 
1622
1813
    def _sha_cutoff_time(self):
1625
1816
 
1626
1817
    def _sha1_file_and_log(self, abspath):
1627
1818
        self._log.append(('sha1', abspath))
1628
 
        return osutils.sha_file_by_name(abspath)
 
1819
        return self._sha1_provider.sha1(abspath)
1629
1820
 
1630
1821
    def _read_link(self, abspath, old_link):
1631
1822
        self._log.append(('read_link', abspath, old_link))
1662
1853
        self.st_ino = ino
1663
1854
        self.st_mode = mode
1664
1855
 
1665
 
 
1666
 
class TestUpdateEntry(TestCaseWithDirState):
1667
 
    """Test the DirState.update_entry functions"""
1668
 
 
1669
 
    def get_state_with_a(self):
1670
 
        """Create a DirState tracking a single object named 'a'"""
1671
 
        state = InstrumentedDirState.initialize('dirstate')
1672
 
        self.addCleanup(state.unlock)
1673
 
        state.add('a', 'a-id', 'file', None, '')
1674
 
        entry = state._get_entry(0, path_utf8='a')
1675
 
        return state, entry
1676
 
 
1677
 
    def test_update_entry(self):
1678
 
        state, entry = self.get_state_with_a()
1679
 
        self.build_tree(['a'])
1680
 
        # Add one where we don't provide the stat or sha already
1681
 
        self.assertEqual(('', 'a', 'a-id'), entry[0])
1682
 
        self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1683
 
                         entry[1])
1684
 
        # Flush the buffers to disk
1685
 
        state.save()
1686
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1687
 
                         state._dirblock_state)
1688
 
 
1689
 
        stat_value = os.lstat('a')
1690
 
        packed_stat = dirstate.pack_stat(stat_value)
1691
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1692
 
                                          stat_value=stat_value)
1693
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1694
 
                         link_or_sha1)
1695
 
 
1696
 
        # The dirblock entry should not cache the file's sha1
1697
 
        self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1698
 
                         entry[1])
1699
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1700
 
                         state._dirblock_state)
1701
 
        mode = stat_value.st_mode
1702
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1703
 
 
1704
 
        state.save()
1705
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1706
 
                         state._dirblock_state)
1707
 
 
1708
 
        # If we do it again right away, we don't know if the file has changed
1709
 
        # so we will re-read the file. Roll the clock back so the file is
1710
 
        # guaranteed to look too new.
1711
 
        state.adjust_time(-10)
1712
 
 
1713
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1714
 
                                          stat_value=stat_value)
1715
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1716
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1717
 
                         ], state._log)
1718
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1719
 
                         link_or_sha1)
1720
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1721
 
                         state._dirblock_state)
1722
 
        self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1723
 
                         entry[1])
1724
 
        state.save()
1725
 
 
1726
 
        # However, if we move the clock forward so the file is considered
1727
 
        # "stable", it should just cache the value.
1728
 
        state.adjust_time(+20)
1729
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1730
 
                                          stat_value=stat_value)
1731
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1732
 
                         link_or_sha1)
1733
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1734
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1735
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1736
 
                         ], state._log)
1737
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1738
 
                         entry[1])
1739
 
 
1740
 
        # Subsequent calls will just return the cached value
1741
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1742
 
                                          stat_value=stat_value)
1743
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1744
 
                         link_or_sha1)
1745
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1746
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1747
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1748
 
                         ], state._log)
1749
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1750
 
                         entry[1])
1751
 
 
1752
 
    def test_update_entry_symlink(self):
1753
 
        """Update entry should read symlinks."""
1754
 
        self.requireFeature(SymlinkFeature)
1755
 
        state, entry = self.get_state_with_a()
1756
 
        state.save()
1757
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1758
 
                         state._dirblock_state)
1759
 
        os.symlink('target', 'a')
1760
 
 
1761
 
        state.adjust_time(-10) # Make the symlink look new
1762
 
        stat_value = os.lstat('a')
1763
 
        packed_stat = dirstate.pack_stat(stat_value)
1764
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1765
 
                                          stat_value=stat_value)
1766
 
        self.assertEqual('target', link_or_sha1)
1767
 
        self.assertEqual([('read_link', 'a', '')], state._log)
1768
 
        # Dirblock is not updated (the link is too new)
1769
 
        self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1770
 
                         entry[1])
1771
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1772
 
                         state._dirblock_state)
1773
 
 
1774
 
        # Because the stat_value looks new, we should re-read the target
1775
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1776
 
                                          stat_value=stat_value)
1777
 
        self.assertEqual('target', link_or_sha1)
1778
 
        self.assertEqual([('read_link', 'a', ''),
1779
 
                          ('read_link', 'a', ''),
1780
 
                         ], state._log)
1781
 
        self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1782
 
                         entry[1])
1783
 
        state.adjust_time(+20) # Skip into the future, all files look old
1784
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1785
 
                                          stat_value=stat_value)
1786
 
        self.assertEqual('target', link_or_sha1)
1787
 
        # We need to re-read the link because only now can we cache it
1788
 
        self.assertEqual([('read_link', 'a', ''),
1789
 
                          ('read_link', 'a', ''),
1790
 
                          ('read_link', 'a', ''),
1791
 
                         ], state._log)
1792
 
        self.assertEqual([('l', 'target', 6, False, packed_stat)],
1793
 
                         entry[1])
1794
 
 
1795
 
        # Another call won't re-read the link
1796
 
        self.assertEqual([('read_link', 'a', ''),
1797
 
                          ('read_link', 'a', ''),
1798
 
                          ('read_link', 'a', ''),
1799
 
                         ], state._log)
1800
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1801
 
                                          stat_value=stat_value)
1802
 
        self.assertEqual('target', link_or_sha1)
1803
 
        self.assertEqual([('l', 'target', 6, False, packed_stat)],
1804
 
                         entry[1])
1805
 
 
1806
 
    def do_update_entry(self, state, entry, abspath):
1807
 
        stat_value = os.lstat(abspath)
1808
 
        return state.update_entry(entry, abspath, stat_value)
1809
 
 
1810
 
    def test_update_entry_dir(self):
1811
 
        state, entry = self.get_state_with_a()
1812
 
        self.build_tree(['a/'])
1813
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1814
 
 
1815
 
    def test_update_entry_dir_unchanged(self):
1816
 
        state, entry = self.get_state_with_a()
1817
 
        self.build_tree(['a/'])
1818
 
        state.adjust_time(+20)
1819
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1820
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1821
 
                         state._dirblock_state)
1822
 
        state.save()
1823
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1824
 
                         state._dirblock_state)
1825
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1826
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1827
 
                         state._dirblock_state)
1828
 
 
1829
 
    def test_update_entry_file_unchanged(self):
1830
 
        state, entry = self.get_state_with_a()
1831
 
        self.build_tree(['a'])
1832
 
        sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1833
 
        state.adjust_time(+20)
1834
 
        self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1835
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1836
 
                         state._dirblock_state)
1837
 
        state.save()
1838
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1839
 
                         state._dirblock_state)
1840
 
        self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1841
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1842
 
                         state._dirblock_state)
1843
 
 
1844
 
    def create_and_test_file(self, state, entry):
1845
 
        """Create a file at 'a' and verify the state finds it.
1846
 
 
1847
 
        The state should already be versioning *something* at 'a'. This makes
1848
 
        sure that state.update_entry recognizes it as a file.
1849
 
        """
1850
 
        self.build_tree(['a'])
1851
 
        stat_value = os.lstat('a')
1852
 
        packed_stat = dirstate.pack_stat(stat_value)
1853
 
 
1854
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1855
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1856
 
                         link_or_sha1)
1857
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1858
 
                         entry[1])
1859
 
        return packed_stat
1860
 
 
1861
 
    def create_and_test_dir(self, state, entry):
1862
 
        """Create a directory at 'a' and verify the state finds it.
1863
 
 
1864
 
        The state should already be versioning *something* at 'a'. This makes
1865
 
        sure that state.update_entry recognizes it as a directory.
1866
 
        """
1867
 
        self.build_tree(['a/'])
1868
 
        stat_value = os.lstat('a')
1869
 
        packed_stat = dirstate.pack_stat(stat_value)
1870
 
 
1871
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1872
 
        self.assertIs(None, link_or_sha1)
1873
 
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1874
 
 
1875
 
        return packed_stat
1876
 
 
1877
 
    def create_and_test_symlink(self, state, entry):
1878
 
        """Create a symlink at 'a' and verify the state finds it.
1879
 
 
1880
 
        The state should already be versioning *something* at 'a'. This makes
1881
 
        sure that state.update_entry recognizes it as a symlink.
1882
 
 
1883
 
        This should not be called if this platform does not have symlink
1884
 
        support.
1885
 
        """
1886
 
        # caller should care about skipping test on platforms without symlinks
1887
 
        os.symlink('path/to/foo', 'a')
1888
 
 
1889
 
        stat_value = os.lstat('a')
1890
 
        packed_stat = dirstate.pack_stat(stat_value)
1891
 
 
1892
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1893
 
        self.assertEqual('path/to/foo', link_or_sha1)
1894
 
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1895
 
                         entry[1])
1896
 
        return packed_stat
1897
 
 
1898
 
    def test_update_file_to_dir(self):
1899
 
        """If a file changes to a directory we return None for the sha.
1900
 
        We also update the inventory record.
1901
 
        """
1902
 
        state, entry = self.get_state_with_a()
1903
 
        # The file sha1 won't be cached unless the file is old
1904
 
        state.adjust_time(+10)
1905
 
        self.create_and_test_file(state, entry)
1906
 
        os.remove('a')
1907
 
        self.create_and_test_dir(state, entry)
1908
 
 
1909
 
    def test_update_file_to_symlink(self):
1910
 
        """File becomes a symlink"""
1911
 
        self.requireFeature(SymlinkFeature)
1912
 
        state, entry = self.get_state_with_a()
1913
 
        # The file sha1 won't be cached unless the file is old
1914
 
        state.adjust_time(+10)
1915
 
        self.create_and_test_file(state, entry)
1916
 
        os.remove('a')
1917
 
        self.create_and_test_symlink(state, entry)
1918
 
 
1919
 
    def test_update_dir_to_file(self):
1920
 
        """Directory becoming a file updates the entry."""
1921
 
        state, entry = self.get_state_with_a()
1922
 
        # The file sha1 won't be cached unless the file is old
1923
 
        state.adjust_time(+10)
1924
 
        self.create_and_test_dir(state, entry)
1925
 
        os.rmdir('a')
1926
 
        self.create_and_test_file(state, entry)
1927
 
 
1928
 
    def test_update_dir_to_symlink(self):
1929
 
        """Directory becomes a symlink"""
1930
 
        self.requireFeature(SymlinkFeature)
1931
 
        state, entry = self.get_state_with_a()
1932
 
        # The symlink target won't be cached if it isn't old
1933
 
        state.adjust_time(+10)
1934
 
        self.create_and_test_dir(state, entry)
1935
 
        os.rmdir('a')
1936
 
        self.create_and_test_symlink(state, entry)
1937
 
 
1938
 
    def test_update_symlink_to_file(self):
1939
 
        """Symlink becomes a file"""
1940
 
        self.requireFeature(SymlinkFeature)
1941
 
        state, entry = self.get_state_with_a()
1942
 
        # The symlink and file info won't be cached unless old
1943
 
        state.adjust_time(+10)
1944
 
        self.create_and_test_symlink(state, entry)
1945
 
        os.remove('a')
1946
 
        self.create_and_test_file(state, entry)
1947
 
 
1948
 
    def test_update_symlink_to_dir(self):
1949
 
        """Symlink becomes a directory"""
1950
 
        self.requireFeature(SymlinkFeature)
1951
 
        state, entry = self.get_state_with_a()
1952
 
        # The symlink target won't be cached if it isn't old
1953
 
        state.adjust_time(+10)
1954
 
        self.create_and_test_symlink(state, entry)
1955
 
        os.remove('a')
1956
 
        self.create_and_test_dir(state, entry)
1957
 
 
1958
 
    def test__is_executable_win32(self):
1959
 
        state, entry = self.get_state_with_a()
1960
 
        self.build_tree(['a'])
1961
 
 
1962
 
        # Make sure we are using the win32 implementation of _is_executable
1963
 
        state._is_executable = state._is_executable_win32
1964
 
 
1965
 
        # The file on disk is not executable, but we are marking it as though
1966
 
        # it is. With _is_executable_win32 we ignore what is on disk.
1967
 
        entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1968
 
 
1969
 
        stat_value = os.lstat('a')
1970
 
        packed_stat = dirstate.pack_stat(stat_value)
1971
 
 
1972
 
        state.adjust_time(-10) # Make sure everything is new
1973
 
        state.update_entry(entry, abspath='a', stat_value=stat_value)
1974
 
 
1975
 
        # The row is updated, but the executable bit stays set.
1976
 
        self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1977
 
                         entry[1])
1978
 
 
1979
 
        # Make the disk object look old enough to cache
1980
 
        state.adjust_time(+20)
1981
 
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1982
 
        state.update_entry(entry, abspath='a', stat_value=stat_value)
1983
 
        self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1984
 
 
1985
 
 
1986
 
class TestPackStat(TestCaseWithTransport):
 
1856
    @staticmethod
 
1857
    def from_stat(st):
 
1858
        return _FakeStat(st.st_size, st.st_mtime, st.st_ctime, st.st_dev,
 
1859
            st.st_ino, st.st_mode)
 
1860
 
 
1861
 
 
1862
class TestPackStat(tests.TestCaseWithTransport):
1987
1863
 
1988
1864
    def assertPackStat(self, expected, stat_value):
1989
1865
        """Check the packed and serialized form of a stat value."""
2054
1930
        # the end it would still be fairly arbitrary, and we don't want the
2055
1931
        # extra overhead if we can avoid it. So sort everything to make sure
2056
1932
        # equality is true
2057
 
        assert len(map_keys) == len(paths)
 
1933
        self.assertEqual(len(map_keys), len(paths))
2058
1934
        expected = {}
2059
1935
        for path, keys in zip(paths, map_keys):
2060
1936
            if keys is None:
2079
1955
        :param paths: A list of directories
2080
1956
        """
2081
1957
        result = state._bisect_dirblocks(paths)
2082
 
        assert len(map_keys) == len(paths)
2083
 
 
 
1958
        self.assertEqual(len(map_keys), len(paths))
2084
1959
        expected = {}
2085
1960
        for path, keys in zip(paths, map_keys):
2086
1961
            if keys is None:
2515
2390
        state._discard_merge_parents()
2516
2391
        state._validate()
2517
2392
        self.assertEqual(exp_dirblocks, state._dirblocks)
 
2393
 
 
2394
 
 
2395
class Test_InvEntryToDetails(tests.TestCase):
 
2396
 
 
2397
    def assertDetails(self, expected, inv_entry):
 
2398
        details = dirstate.DirState._inv_entry_to_details(inv_entry)
 
2399
        self.assertEqual(expected, details)
 
2400
        # details should always allow join() and always be a plain str when
 
2401
        # finished
 
2402
        (minikind, fingerprint, size, executable, tree_data) = details
 
2403
        self.assertIsInstance(minikind, str)
 
2404
        self.assertIsInstance(fingerprint, str)
 
2405
        self.assertIsInstance(tree_data, str)
 
2406
 
 
2407
    def test_unicode_symlink(self):
 
2408
        inv_entry = inventory.InventoryLink('link-file-id',
 
2409
                                            u'nam\N{Euro Sign}e',
 
2410
                                            'link-parent-id')
 
2411
        inv_entry.revision = 'link-revision-id'
 
2412
        target = u'link-targ\N{Euro Sign}t'
 
2413
        inv_entry.symlink_target = target
 
2414
        self.assertDetails(('l', target.encode('UTF-8'), 0, False,
 
2415
                            'link-revision-id'), inv_entry)
 
2416
 
 
2417
 
 
2418
class TestSHA1Provider(tests.TestCaseInTempDir):
 
2419
 
 
2420
    def test_sha1provider_is_an_interface(self):
 
2421
        p = dirstate.SHA1Provider()
 
2422
        self.assertRaises(NotImplementedError, p.sha1, "foo")
 
2423
        self.assertRaises(NotImplementedError, p.stat_and_sha1, "foo")
 
2424
 
 
2425
    def test_defaultsha1provider_sha1(self):
 
2426
        text = 'test\r\nwith\nall\rpossible line endings\r\n'
 
2427
        self.build_tree_contents([('foo', text)])
 
2428
        expected_sha = osutils.sha_string(text)
 
2429
        p = dirstate.DefaultSHA1Provider()
 
2430
        self.assertEqual(expected_sha, p.sha1('foo'))
 
2431
 
 
2432
    def test_defaultsha1provider_stat_and_sha1(self):
 
2433
        text = 'test\r\nwith\nall\rpossible line endings\r\n'
 
2434
        self.build_tree_contents([('foo', text)])
 
2435
        expected_sha = osutils.sha_string(text)
 
2436
        p = dirstate.DefaultSHA1Provider()
 
2437
        statvalue, sha1 = p.stat_and_sha1('foo')
 
2438
        self.assertTrue(len(statvalue) >= 10)
 
2439
        self.assertEqual(len(text), statvalue.st_size)
 
2440
        self.assertEqual(expected_sha, sha1)
 
2441
 
 
2442
 
 
2443
class _Repo(object):
 
2444
    """A minimal api to get InventoryRevisionTree to work."""
 
2445
 
 
2446
    def __init__(self):
 
2447
        default_format = bzrdir.format_registry.make_bzrdir('default')
 
2448
        self._format = default_format.repository_format
 
2449
 
 
2450
    def lock_read(self):
 
2451
        pass
 
2452
 
 
2453
    def unlock(self):
 
2454
        pass
 
2455
 
 
2456
 
 
2457
class TestUpdateBasisByDelta(tests.TestCase):
 
2458
 
 
2459
    def path_to_ie(self, path, file_id, rev_id, dir_ids):
 
2460
        if path.endswith('/'):
 
2461
            is_dir = True
 
2462
            path = path[:-1]
 
2463
        else:
 
2464
            is_dir = False
 
2465
        dirname, basename = osutils.split(path)
 
2466
        try:
 
2467
            dir_id = dir_ids[dirname]
 
2468
        except KeyError:
 
2469
            dir_id = osutils.basename(dirname) + '-id'
 
2470
        if is_dir:
 
2471
            ie = inventory.InventoryDirectory(file_id, basename, dir_id)
 
2472
            dir_ids[path] = file_id
 
2473
        else:
 
2474
            ie = inventory.InventoryFile(file_id, basename, dir_id)
 
2475
            ie.text_size = 0
 
2476
            ie.text_sha1 = ''
 
2477
        ie.revision = rev_id
 
2478
        return ie
 
2479
 
 
2480
    def create_tree_from_shape(self, rev_id, shape):
 
2481
        dir_ids = {'': 'root-id'}
 
2482
        inv = inventory.Inventory('root-id', rev_id)
 
2483
        for path, file_id in shape:
 
2484
            if path == '':
 
2485
                # Replace the root entry
 
2486
                del inv._byid[inv.root.file_id]
 
2487
                inv.root.file_id = file_id
 
2488
                inv._byid[file_id] = inv.root
 
2489
                dir_ids[''] = file_id
 
2490
                continue
 
2491
            inv.add(self.path_to_ie(path, file_id, rev_id, dir_ids))
 
2492
        return revisiontree.InventoryRevisionTree(_Repo(), inv, rev_id)
 
2493
 
 
2494
    def create_empty_dirstate(self):
 
2495
        fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
 
2496
        self.addCleanup(os.remove, path)
 
2497
        os.close(fd)
 
2498
        state = dirstate.DirState.initialize(path)
 
2499
        self.addCleanup(state.unlock)
 
2500
        return state
 
2501
 
 
2502
    def create_inv_delta(self, delta, rev_id):
 
2503
        """Translate a 'delta shape' into an actual InventoryDelta"""
 
2504
        dir_ids = {'': 'root-id'}
 
2505
        inv_delta = []
 
2506
        for old_path, new_path, file_id in delta:
 
2507
            if old_path is not None and old_path.endswith('/'):
 
2508
                # Don't have to actually do anything for this, because only
 
2509
                # new_path creates InventoryEntries
 
2510
                old_path = old_path[:-1]
 
2511
            if new_path is None: # Delete
 
2512
                inv_delta.append((old_path, None, file_id, None))
 
2513
                continue
 
2514
            ie = self.path_to_ie(new_path, file_id, rev_id, dir_ids)
 
2515
            inv_delta.append((old_path, new_path, file_id, ie))
 
2516
        return inv_delta
 
2517
 
 
2518
    def assertUpdate(self, active, basis, target):
 
2519
        """Assert that update_basis_by_delta works how we want.
 
2520
 
 
2521
        Set up a DirState object with active_shape for tree 0, basis_shape for
 
2522
        tree 1. Then apply the delta from basis_shape to target_shape,
 
2523
        and assert that the DirState is still valid, and that its stored
 
2524
        content matches the target_shape.
 
2525
        """
 
2526
        active_tree = self.create_tree_from_shape('active', active)
 
2527
        basis_tree = self.create_tree_from_shape('basis', basis)
 
2528
        target_tree = self.create_tree_from_shape('target', target)
 
2529
        state = self.create_empty_dirstate()
 
2530
        state.set_state_from_scratch(active_tree.inventory,
 
2531
            [('basis', basis_tree)], [])
 
2532
        delta = target_tree.inventory._make_delta(basis_tree.inventory)
 
2533
        state.update_basis_by_delta(delta, 'target')
 
2534
        state._validate()
 
2535
        dirstate_tree = workingtree_4.DirStateRevisionTree(state,
 
2536
            'target', _Repo())
 
2537
        # The target now that delta has been applied should match the
 
2538
        # RevisionTree
 
2539
        self.assertEqual([], list(dirstate_tree.iter_changes(target_tree)))
 
2540
        # And the dirblock state should be identical to the state if we created
 
2541
        # it from scratch.
 
2542
        state2 = self.create_empty_dirstate()
 
2543
        state2.set_state_from_scratch(active_tree.inventory,
 
2544
            [('target', target_tree)], [])
 
2545
        self.assertEqual(state2._dirblocks, state._dirblocks)
 
2546
        return state
 
2547
 
 
2548
    def assertBadDelta(self, active, basis, delta):
 
2549
        """Test that we raise InconsistentDelta when appropriate.
 
2550
 
 
2551
        :param active: The active tree shape
 
2552
        :param basis: The basis tree shape
 
2553
        :param delta: A description of the delta to apply. Similar to the form
 
2554
            for regular inventory deltas, but omitting the InventoryEntry.
 
2555
            So adding a file is: (None, 'path', 'file-id')
 
2556
            Adding a directory is: (None, 'path/', 'dir-id')
 
2557
            Renaming a dir is: ('old/', 'new/', 'dir-id')
 
2558
            etc.
 
2559
        """
 
2560
        active_tree = self.create_tree_from_shape('active', active)
 
2561
        basis_tree = self.create_tree_from_shape('basis', basis)
 
2562
        inv_delta = self.create_inv_delta(delta, 'target')
 
2563
        state = self.create_empty_dirstate()
 
2564
        state.set_state_from_scratch(active_tree.inventory,
 
2565
            [('basis', basis_tree)], [])
 
2566
        self.assertRaises(errors.InconsistentDelta,
 
2567
            state.update_basis_by_delta, inv_delta, 'target')
 
2568
        ## try:
 
2569
        ##     state.update_basis_by_delta(inv_delta, 'target')
 
2570
        ## except errors.InconsistentDelta, e:
 
2571
        ##     import pdb; pdb.set_trace()
 
2572
        ## else:
 
2573
        ##     import pdb; pdb.set_trace()
 
2574
        self.assertTrue(state._changes_aborted)
 
2575
 
 
2576
    def test_remove_file_matching_active_state(self):
 
2577
        state = self.assertUpdate(
 
2578
            active=[],
 
2579
            basis =[('file', 'file-id')],
 
2580
            target=[],
 
2581
            )
 
2582
 
 
2583
    def test_remove_file_present_in_active_state(self):
 
2584
        state = self.assertUpdate(
 
2585
            active=[('file', 'file-id')],
 
2586
            basis =[('file', 'file-id')],
 
2587
            target=[],
 
2588
            )
 
2589
 
 
2590
    def test_remove_file_present_elsewhere_in_active_state(self):
 
2591
        state = self.assertUpdate(
 
2592
            active=[('other-file', 'file-id')],
 
2593
            basis =[('file', 'file-id')],
 
2594
            target=[],
 
2595
            )
 
2596
 
 
2597
    def test_remove_file_active_state_has_diff_file(self):
 
2598
        state = self.assertUpdate(
 
2599
            active=[('file', 'file-id-2')],
 
2600
            basis =[('file', 'file-id')],
 
2601
            target=[],
 
2602
            )
 
2603
 
 
2604
    def test_remove_file_active_state_has_diff_file_and_file_elsewhere(self):
 
2605
        state = self.assertUpdate(
 
2606
            active=[('file', 'file-id-2'),
 
2607
                    ('other-file', 'file-id')],
 
2608
            basis =[('file', 'file-id')],
 
2609
            target=[],
 
2610
            )
 
2611
 
 
2612
    def test_add_file_matching_active_state(self):
 
2613
        state = self.assertUpdate(
 
2614
            active=[('file', 'file-id')],
 
2615
            basis =[],
 
2616
            target=[('file', 'file-id')],
 
2617
            )
 
2618
 
 
2619
    def test_add_file_missing_in_active_state(self):
 
2620
        state = self.assertUpdate(
 
2621
            active=[],
 
2622
            basis =[],
 
2623
            target=[('file', 'file-id')],
 
2624
            )
 
2625
 
 
2626
    def test_add_file_elsewhere_in_active_state(self):
 
2627
        state = self.assertUpdate(
 
2628
            active=[('other-file', 'file-id')],
 
2629
            basis =[],
 
2630
            target=[('file', 'file-id')],
 
2631
            )
 
2632
 
 
2633
    def test_add_file_active_state_has_diff_file_and_file_elsewhere(self):
 
2634
        state = self.assertUpdate(
 
2635
            active=[('other-file', 'file-id'),
 
2636
                    ('file', 'file-id-2')],
 
2637
            basis =[],
 
2638
            target=[('file', 'file-id')],
 
2639
            )
 
2640
 
 
2641
    def test_rename_file_matching_active_state(self):
 
2642
        state = self.assertUpdate(
 
2643
            active=[('other-file', 'file-id')],
 
2644
            basis =[('file', 'file-id')],
 
2645
            target=[('other-file', 'file-id')],
 
2646
            )
 
2647
 
 
2648
    def test_rename_file_missing_in_active_state(self):
 
2649
        state = self.assertUpdate(
 
2650
            active=[],
 
2651
            basis =[('file', 'file-id')],
 
2652
            target=[('other-file', 'file-id')],
 
2653
            )
 
2654
 
 
2655
    def test_rename_file_present_elsewhere_in_active_state(self):
 
2656
        state = self.assertUpdate(
 
2657
            active=[('third', 'file-id')],
 
2658
            basis =[('file', 'file-id')],
 
2659
            target=[('other-file', 'file-id')],
 
2660
            )
 
2661
 
 
2662
    def test_rename_file_active_state_has_diff_source_file(self):
 
2663
        state = self.assertUpdate(
 
2664
            active=[('file', 'file-id-2')],
 
2665
            basis =[('file', 'file-id')],
 
2666
            target=[('other-file', 'file-id')],
 
2667
            )
 
2668
 
 
2669
    def test_rename_file_active_state_has_diff_target_file(self):
 
2670
        state = self.assertUpdate(
 
2671
            active=[('other-file', 'file-id-2')],
 
2672
            basis =[('file', 'file-id')],
 
2673
            target=[('other-file', 'file-id')],
 
2674
            )
 
2675
 
 
2676
    def test_rename_file_active_has_swapped_files(self):
 
2677
        state = self.assertUpdate(
 
2678
            active=[('file', 'file-id'),
 
2679
                    ('other-file', 'file-id-2')],
 
2680
            basis= [('file', 'file-id'),
 
2681
                    ('other-file', 'file-id-2')],
 
2682
            target=[('file', 'file-id-2'),
 
2683
                    ('other-file', 'file-id')])
 
2684
 
 
2685
    def test_rename_file_basis_has_swapped_files(self):
 
2686
        state = self.assertUpdate(
 
2687
            active=[('file', 'file-id'),
 
2688
                    ('other-file', 'file-id-2')],
 
2689
            basis= [('file', 'file-id-2'),
 
2690
                    ('other-file', 'file-id')],
 
2691
            target=[('file', 'file-id'),
 
2692
                    ('other-file', 'file-id-2')])
 
2693
 
 
2694
    def test_rename_directory_with_contents(self):
 
2695
        state = self.assertUpdate( # active matches basis
 
2696
            active=[('dir1/', 'dir-id'),
 
2697
                    ('dir1/file', 'file-id')],
 
2698
            basis= [('dir1/', 'dir-id'),
 
2699
                    ('dir1/file', 'file-id')],
 
2700
            target=[('dir2/', 'dir-id'),
 
2701
                    ('dir2/file', 'file-id')])
 
2702
        state = self.assertUpdate( # active matches target
 
2703
            active=[('dir2/', 'dir-id'),
 
2704
                    ('dir2/file', 'file-id')],
 
2705
            basis= [('dir1/', 'dir-id'),
 
2706
                    ('dir1/file', 'file-id')],
 
2707
            target=[('dir2/', 'dir-id'),
 
2708
                    ('dir2/file', 'file-id')])
 
2709
        state = self.assertUpdate( # active empty
 
2710
            active=[],
 
2711
            basis= [('dir1/', 'dir-id'),
 
2712
                    ('dir1/file', 'file-id')],
 
2713
            target=[('dir2/', 'dir-id'),
 
2714
                    ('dir2/file', 'file-id')])
 
2715
        state = self.assertUpdate( # active present at other location
 
2716
            active=[('dir3/', 'dir-id'),
 
2717
                    ('dir3/file', 'file-id')],
 
2718
            basis= [('dir1/', 'dir-id'),
 
2719
                    ('dir1/file', 'file-id')],
 
2720
            target=[('dir2/', 'dir-id'),
 
2721
                    ('dir2/file', 'file-id')])
 
2722
        state = self.assertUpdate( # active has different ids
 
2723
            active=[('dir1/', 'dir1-id'),
 
2724
                    ('dir1/file', 'file1-id'),
 
2725
                    ('dir2/', 'dir2-id'),
 
2726
                    ('dir2/file', 'file2-id')],
 
2727
            basis= [('dir1/', 'dir-id'),
 
2728
                    ('dir1/file', 'file-id')],
 
2729
            target=[('dir2/', 'dir-id'),
 
2730
                    ('dir2/file', 'file-id')])
 
2731
 
 
2732
    def test_invalid_file_not_present(self):
 
2733
        state = self.assertBadDelta(
 
2734
            active=[('file', 'file-id')],
 
2735
            basis= [('file', 'file-id')],
 
2736
            delta=[('other-file', 'file', 'file-id')])
 
2737
 
 
2738
    def test_invalid_new_id_same_path(self):
 
2739
        # The bad entry comes after
 
2740
        state = self.assertBadDelta(
 
2741
            active=[('file', 'file-id')],
 
2742
            basis= [('file', 'file-id')],
 
2743
            delta=[(None, 'file', 'file-id-2')])
 
2744
        # The bad entry comes first
 
2745
        state = self.assertBadDelta(
 
2746
            active=[('file', 'file-id-2')],
 
2747
            basis=[('file', 'file-id-2')],
 
2748
            delta=[(None, 'file', 'file-id')])
 
2749
 
 
2750
    def test_invalid_existing_id(self):
 
2751
        state = self.assertBadDelta(
 
2752
            active=[('file', 'file-id')],
 
2753
            basis= [('file', 'file-id')],
 
2754
            delta=[(None, 'file', 'file-id')])
 
2755
 
 
2756
    def test_invalid_parent_missing(self):
 
2757
        state = self.assertBadDelta(
 
2758
            active=[],
 
2759
            basis= [],
 
2760
            delta=[(None, 'path/path2', 'file-id')])
 
2761
        # Note: we force the active tree to have the directory, by knowing how
 
2762
        #       path_to_ie handles entries with missing parents
 
2763
        state = self.assertBadDelta(
 
2764
            active=[('path/', 'path-id')],
 
2765
            basis= [],
 
2766
            delta=[(None, 'path/path2', 'file-id')])
 
2767
        state = self.assertBadDelta(
 
2768
            active=[('path/', 'path-id'),
 
2769
                    ('path/path2', 'file-id')],
 
2770
            basis= [],
 
2771
            delta=[(None, 'path/path2', 'file-id')])
 
2772
 
 
2773
    def test_renamed_dir_same_path(self):
 
2774
        # We replace the parent directory, with another parent dir. But the C
 
2775
        # file doesn't look like it has been moved.
 
2776
        state = self.assertUpdate(# Same as basis
 
2777
            active=[('dir/', 'A-id'),
 
2778
                    ('dir/B', 'B-id')],
 
2779
            basis= [('dir/', 'A-id'),
 
2780
                    ('dir/B', 'B-id')],
 
2781
            target=[('dir/', 'C-id'),
 
2782
                    ('dir/B', 'B-id')])
 
2783
        state = self.assertUpdate(# Same as target
 
2784
            active=[('dir/', 'C-id'),
 
2785
                    ('dir/B', 'B-id')],
 
2786
            basis= [('dir/', 'A-id'),
 
2787
                    ('dir/B', 'B-id')],
 
2788
            target=[('dir/', 'C-id'),
 
2789
                    ('dir/B', 'B-id')])
 
2790
        state = self.assertUpdate(# empty active
 
2791
            active=[],
 
2792
            basis= [('dir/', 'A-id'),
 
2793
                    ('dir/B', 'B-id')],
 
2794
            target=[('dir/', 'C-id'),
 
2795
                    ('dir/B', 'B-id')])
 
2796
        state = self.assertUpdate(# different active
 
2797
            active=[('dir/', 'D-id'),
 
2798
                    ('dir/B', 'B-id')],
 
2799
            basis= [('dir/', 'A-id'),
 
2800
                    ('dir/B', 'B-id')],
 
2801
            target=[('dir/', 'C-id'),
 
2802
                    ('dir/B', 'B-id')])
 
2803
 
 
2804
    def test_parent_child_swap(self):
 
2805
        state = self.assertUpdate(# Same as basis
 
2806
            active=[('A/', 'A-id'),
 
2807
                    ('A/B/', 'B-id'),
 
2808
                    ('A/B/C', 'C-id')],
 
2809
            basis= [('A/', 'A-id'),
 
2810
                    ('A/B/', 'B-id'),
 
2811
                    ('A/B/C', 'C-id')],
 
2812
            target=[('A/', 'B-id'),
 
2813
                    ('A/B/', 'A-id'),
 
2814
                    ('A/B/C', 'C-id')])
 
2815
        state = self.assertUpdate(# Same as target
 
2816
            active=[('A/', 'B-id'),
 
2817
                    ('A/B/', 'A-id'),
 
2818
                    ('A/B/C', 'C-id')],
 
2819
            basis= [('A/', 'A-id'),
 
2820
                    ('A/B/', 'B-id'),
 
2821
                    ('A/B/C', 'C-id')],
 
2822
            target=[('A/', 'B-id'),
 
2823
                    ('A/B/', 'A-id'),
 
2824
                    ('A/B/C', 'C-id')])
 
2825
        state = self.assertUpdate(# empty active
 
2826
            active=[],
 
2827
            basis= [('A/', 'A-id'),
 
2828
                    ('A/B/', 'B-id'),
 
2829
                    ('A/B/C', 'C-id')],
 
2830
            target=[('A/', 'B-id'),
 
2831
                    ('A/B/', 'A-id'),
 
2832
                    ('A/B/C', 'C-id')])
 
2833
        state = self.assertUpdate(# different active
 
2834
            active=[('D/', 'A-id'),
 
2835
                    ('D/E/', 'B-id'),
 
2836
                    ('F', 'C-id')],
 
2837
            basis= [('A/', 'A-id'),
 
2838
                    ('A/B/', 'B-id'),
 
2839
                    ('A/B/C', 'C-id')],
 
2840
            target=[('A/', 'B-id'),
 
2841
                    ('A/B/', 'A-id'),
 
2842
                    ('A/B/C', 'C-id')])
 
2843
 
 
2844
    def test_change_root_id(self):
 
2845
        state = self.assertUpdate( # same as basis
 
2846
            active=[('', 'root-id'),
 
2847
                    ('file', 'file-id')],
 
2848
            basis= [('', 'root-id'),
 
2849
                    ('file', 'file-id')],
 
2850
            target=[('', 'target-root-id'),
 
2851
                    ('file', 'file-id')])
 
2852
        state = self.assertUpdate( # same as target
 
2853
            active=[('', 'target-root-id'),
 
2854
                    ('file', 'file-id')],
 
2855
            basis= [('', 'root-id'),
 
2856
                    ('file', 'file-id')],
 
2857
            target=[('', 'target-root-id'),
 
2858
                    ('file', 'root-id')])
 
2859
        state = self.assertUpdate( # all different
 
2860
            active=[('', 'active-root-id'),
 
2861
                    ('file', 'file-id')],
 
2862
            basis= [('', 'root-id'),
 
2863
                    ('file', 'file-id')],
 
2864
            target=[('', 'target-root-id'),
 
2865
                    ('file', 'root-id')])
 
2866
 
 
2867
    def test_change_file_absent_in_active(self):
 
2868
        state = self.assertUpdate(
 
2869
            active=[],
 
2870
            basis= [('file', 'file-id')],
 
2871
            target=[('file', 'file-id')])
 
2872
 
 
2873
    def test_invalid_changed_file(self):
 
2874
        state = self.assertBadDelta( # Not present in basis
 
2875
            active=[('file', 'file-id')],
 
2876
            basis= [],
 
2877
            delta=[('file', 'file', 'file-id')])
 
2878
        state = self.assertBadDelta( # present at another location in basis
 
2879
            active=[('file', 'file-id')],
 
2880
            basis= [('other-file', 'file-id')],
 
2881
            delta=[('file', 'file', 'file-id')])