~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_dirstate.py

  • Committer: Patch Queue Manager
  • Date: 2016-04-21 05:06:57 UTC
  • mfrom: (6603.4.1 bzr)
  • Revision ID: pqm@pqm.ubuntu.com-20160421050657-ygnzfybewvudf1j9
(richard-wilbur) Use initial_comment as commit_message for lp_propose.(Shawn
 Wang) (Shawn Wang)

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