~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 04:10:52 UTC
  • mfrom: (6616.1.1 fix-en-user-guide)
  • Revision ID: pqm@pqm.ubuntu.com-20160421041052-clcye7ns1qcl2n7w
(richard-wilbur) Ensure build of English use guide always uses English text
 even when user's locale specifies a different language. (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
    controldir,
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
29
34
from bzrlib.tests import (
30
 
        SymlinkFeature,
31
 
        TestCase,
32
 
        TestCaseWithTransport,
33
 
        )
 
35
    features,
 
36
    test_osutils,
 
37
    )
 
38
from bzrlib.tests.scenarios import load_tests_apply_scenarios
34
39
 
35
40
 
36
41
# TODO:
46
51
# set_path_id  setting id when state is in memory modified
47
52
 
48
53
 
49
 
class TestCaseWithDirState(TestCaseWithTransport):
 
54
load_tests = load_tests_apply_scenarios
 
55
 
 
56
 
 
57
class TestCaseWithDirState(tests.TestCaseWithTransport):
50
58
    """Helper functions for creating DirState objects with various content."""
51
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
 
52
71
    def create_empty_dirstate(self):
53
72
        """Return a locked but empty dirstate"""
54
73
        state = dirstate.DirState.initialize('dirstate')
162
181
        """
163
182
        # The state should already be write locked, since we just had to do
164
183
        # some operation to get here.
165
 
        assert state._lock_token is not None
 
184
        self.assertTrue(state._lock_token is not None)
166
185
        try:
167
186
            self.assertEqual(expected_result[0],  state.get_parent_ids())
168
187
            # there should be no ghosts in this tree.
395
414
            (('', '', tree.get_root_id()), # common details
396
415
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
397
416
              ('d', '', 0, False, rev_id), # first parent details
398
 
              ('d', '', 0, False, rev_id2), # second parent details
 
417
              ('d', '', 0, False, rev_id), # second parent details
399
418
             ])])
400
419
        state = dirstate.DirState.from_tree(tree, 'dirstate')
401
420
        self.check_state_with_reopen(expected_result, state)
420
439
    def get_tree_with_a_file(self):
421
440
        tree = self.make_branch_and_tree('tree')
422
441
        self.build_tree(['tree/a file'])
423
 
        tree.add('a file', 'a file id')
 
442
        tree.add('a file', 'a-file-id')
424
443
        return tree
425
444
 
426
445
    def test_non_empty_no_parents_to_dirstate(self):
431
450
            (('', '', tree.get_root_id()), # common details
432
451
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
433
452
             ]),
434
 
            (('', 'a file', 'a file id'), # common
 
453
            (('', 'a file', 'a-file-id'), # common
435
454
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
436
455
             ]),
437
456
            ])
450
469
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
451
470
              ('d', '', 0, False, rev_id), # first parent details
452
471
             ]),
453
 
            (('', 'a file', 'a file id'), # common
 
472
            (('', 'a file', 'a-file-id'), # common
454
473
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
455
474
              ('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
456
475
               rev_id), # first parent
476
495
            (('', '', tree.get_root_id()), # common details
477
496
             [('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
478
497
              ('d', '', 0, False, rev_id), # first parent details
479
 
              ('d', '', 0, False, rev_id2), # second parent details
 
498
              ('d', '', 0, False, rev_id), # second parent details
480
499
             ]),
481
 
            (('', 'a file', 'a file id'), # common
 
500
            (('', 'a file', 'a-file-id'), # common
482
501
             [('f', '', 0, False, dirstate.DirState.NULLSTAT), # current
483
502
              ('f', 'c3ed76e4bfd45ff1763ca206055bca8e9fc28aa8', 24, False,
484
503
               rev_id), # first parent
514
533
 
515
534
class TestDirStateOnFile(TestCaseWithDirState):
516
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
 
517
549
    def test_construct_with_path(self):
518
550
        tree = self.make_branch_and_tree('tree')
519
551
        state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
548
580
            state.unlock()
549
581
 
550
582
    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()
 
583
        state = self.create_updated_dirstate()
563
584
        try:
564
585
            entry = state._get_entry(0, path_utf8='a-file')
565
 
            # The current sha1 sum should be empty
566
 
            self.assertEqual('', entry[1][0][1])
 
586
            # The current size should be 0 (default)
 
587
            self.assertEqual(0, entry[1][0][2])
567
588
            # We should have a real entry.
568
589
            self.assertNotEqual((None, None), entry)
569
 
            # Make sure everything is old enough
 
590
            # Set the cutoff-time into the future, so things look cacheable
570
591
            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
 
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
574
596
            self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
575
597
                             sha1sum)
576
598
 
577
599
            # The dirblock has been updated
578
 
            self.assertEqual(sha1sum, entry[1][0][1])
579
 
            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,
580
602
                             state._dirblock_state)
581
603
 
582
604
            del entry
591
613
        state.lock_read()
592
614
        try:
593
615
            entry = state._get_entry(0, path_utf8='a-file')
594
 
            self.assertEqual(sha1sum, entry[1][0][1])
 
616
            self.assertEqual(st.st_size, entry[1][0][2])
595
617
        finally:
596
618
            state.unlock()
597
619
 
598
620
    def test_save_fails_quietly_if_locked(self):
599
621
        """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()
 
622
        state = self.create_updated_dirstate()
611
623
        try:
612
624
            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
 
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)
615
632
            self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
616
633
                             sha1sum)
617
 
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
634
            self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
618
635
                             state._dirblock_state)
619
636
 
620
637
            # Now, before we try to save, grab another dirstate, and take out a
636
653
                state2.unlock()
637
654
        finally:
638
655
            state.unlock()
639
 
        
 
656
 
640
657
        # The file on disk should not be modified.
641
658
        state = dirstate.DirState.on_file('dirstate')
642
659
        state.lock_read()
646
663
        finally:
647
664
            state.unlock()
648
665
 
 
666
    def test_save_refuses_if_changes_aborted(self):
 
667
        self.build_tree(['a-file', 'a-dir/'])
 
668
        state = dirstate.DirState.initialize('dirstate')
 
669
        try:
 
670
            # No stat and no sha1 sum.
 
671
            state.add('a-file', 'a-file-id', 'file', None, '')
 
672
            state.save()
 
673
        finally:
 
674
            state.unlock()
 
675
 
 
676
        # The dirstate should include TREE_ROOT and 'a-file' and nothing else
 
677
        expected_blocks = [
 
678
            ('', [(('', '', 'TREE_ROOT'),
 
679
                   [('d', '', 0, False, dirstate.DirState.NULLSTAT)])]),
 
680
            ('', [(('', 'a-file', 'a-file-id'),
 
681
                   [('f', '', 0, False, dirstate.DirState.NULLSTAT)])]),
 
682
        ]
 
683
 
 
684
        state = dirstate.DirState.on_file('dirstate')
 
685
        state.lock_write()
 
686
        try:
 
687
            state._read_dirblocks_if_needed()
 
688
            self.assertEqual(expected_blocks, state._dirblocks)
 
689
 
 
690
            # Now modify the state, but mark it as inconsistent
 
691
            state.add('a-dir', 'a-dir-id', 'directory', None, '')
 
692
            state._changes_aborted = True
 
693
            state.save()
 
694
        finally:
 
695
            state.unlock()
 
696
 
 
697
        state = dirstate.DirState.on_file('dirstate')
 
698
        state.lock_read()
 
699
        try:
 
700
            state._read_dirblocks_if_needed()
 
701
            self.assertEqual(expected_blocks, state._dirblocks)
 
702
        finally:
 
703
            state.unlock()
 
704
 
649
705
 
650
706
class TestDirStateInitialize(TestCaseWithDirState):
651
707
 
671
727
 
672
728
class TestDirStateManipulations(TestCaseWithDirState):
673
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
 
674
754
    def test_set_state_from_inventory_no_content_no_parents(self):
675
755
        # setting the current inventory is a slow but important api to support.
676
 
        tree1 = self.make_branch_and_memory_tree('tree1')
677
 
        tree1.lock_write()
678
 
        try:
679
 
            tree1.add('')
680
 
            revid1 = tree1.commit('foo').encode('utf8')
681
 
            root_id = tree1.get_root_id()
682
 
            inv = tree1.inventory
683
 
        finally:
684
 
            tree1.unlock()
 
756
        tree1, revid1 = self.make_minimal_tree()
 
757
        inv = tree1.root_inventory
 
758
        root_id = inv.path2id('')
685
759
        expected_result = [], [
686
760
            (('', '', root_id), [
687
761
             ('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
699
773
            # This will unlock it
700
774
            self.check_state_with_reopen(expected_result, state)
701
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
 
702
820
    def test_set_state_from_inventory_preserves_hashcache(self):
703
821
        # https://bugs.launchpad.net/bzr/+bug/146176
704
822
        # set_state_from_inventory should preserve the stat and hash value for
705
823
        # workingtree files that are not changed by the inventory.
706
 
       
 
824
 
707
825
        tree = self.make_branch_and_tree('.')
708
826
        # depends on the default format using dirstate...
709
827
        tree.lock_write()
710
828
        try:
711
 
            # make a dirstate with some valid hashcache data 
 
829
            # make a dirstate with some valid hashcache data
712
830
            # file on disk, but that's not needed for this test
713
831
            foo_contents = 'contents of foo'
714
832
            self.build_tree_contents([('foo', foo_contents)])
734
852
                (('', 'foo', 'foo-id',),
735
853
                 [('f', foo_sha, foo_size, False, foo_packed)]),
736
854
                tree._dirstate._get_entry(0, 'foo-id'))
737
 
           
 
855
 
738
856
            # extract the inventory, and add something to it
739
 
            inv = tree._get_inventory()
 
857
            inv = tree._get_root_inventory()
740
858
            # should see the file we poked in...
741
859
            self.assertTrue(inv.has_id('foo-id'))
742
860
            self.assertTrue(inv.has_filename('foo'))
762
880
        finally:
763
881
            tree.unlock()
764
882
 
765
 
 
766
883
    def test_set_state_from_inventory_mixed_paths(self):
767
884
        tree1 = self.make_branch_and_tree('tree1')
768
885
        self.build_tree(['tree1/a/', 'tree1/a/b/', 'tree1/a-b/',
773
890
                      ['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
774
891
            tree1.commit('rev1', rev_id='rev1')
775
892
            root_id = tree1.get_root_id()
776
 
            inv = tree1.inventory
 
893
            inv = tree1.root_inventory
777
894
        finally:
778
895
            tree1.unlock()
779
896
        expected_result1 = [('', '', root_id, 'd'),
809
926
        state = dirstate.DirState.initialize('dirstate')
810
927
        try:
811
928
            # check precondition to be sure the state does change appropriately.
812
 
            self.assertEqual(
813
 
                [(('', '', 'TREE_ROOT'), [('d', '', 0, False,
814
 
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])],
815
 
                list(state._iter_entries()))
816
 
            state.set_path_id('', 'foobarbaz')
817
 
            expected_rows = [
818
 
                (('', '', 'foobarbaz'), [('d', '', 0, False,
819
 
                   'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx')])]
 
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]
820
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'))
821
946
            # should work across save too
822
947
            state.save()
823
948
        finally:
841
966
        state._validate()
842
967
        try:
843
968
            state.set_parent_trees([('parent-revid', rt)], ghosts=[])
844
 
            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')
845
978
            state._validate()
846
979
            # now see that it is what we expected
847
 
            expected_rows = [
848
 
                (('', '', 'TREE_ROOT'),
849
 
                    [('a', '', 0, False, ''),
850
 
                     ('d', '', 0, False, 'parent-revid'),
851
 
                     ]),
852
 
                (('', '', 'foobarbaz'),
853
 
                    [('d', '', 0, False, ''),
854
 
                     ('a', '', 0, False, ''),
855
 
                     ]),
856
 
                ]
 
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]
857
987
            state._validate()
858
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'))
859
999
            # should work across save too
860
1000
            state.save()
861
1001
        finally:
877
1017
        finally:
878
1018
            state.unlock()
879
1019
 
880
 
 
881
1020
    def test_set_parent_trees_no_content(self):
882
1021
        # set_parent_trees is a slow but important api to support.
883
1022
        tree1 = self.make_branch_and_memory_tree('tree1')
888
1027
        finally:
889
1028
            tree1.unlock()
890
1029
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
891
 
        tree2 = MemoryTree.create_on_branch(branch2)
 
1030
        tree2 = memorytree.MemoryTree.create_on_branch(branch2)
892
1031
        tree2.lock_write()
893
1032
        try:
894
1033
            revid2 = tree2.commit('foo')
926
1065
            state.set_parent_trees(
927
1066
                ((revid1, tree1.branch.repository.revision_tree(revid1)),
928
1067
                 (revid2, tree2.branch.repository.revision_tree(revid2)),
929
 
                 ('ghost-rev', tree2.branch.repository.revision_tree(None))),
 
1068
                 ('ghost-rev', tree2.branch.repository.revision_tree(
 
1069
                                   _mod_revision.NULL_REVISION))),
930
1070
                ['ghost-rev'])
931
1071
            self.assertEqual([revid1, revid2, 'ghost-rev'],
932
1072
                             state.get_parent_ids())
936
1076
                [(('', '', root_id), [
937
1077
                  ('d', '', 0, False, dirstate.DirState.NULLSTAT),
938
1078
                  ('d', '', 0, False, revid1),
939
 
                  ('d', '', 0, False, revid2)
 
1079
                  ('d', '', 0, False, revid1)
940
1080
                  ])],
941
1081
                list(state._iter_entries()))
942
1082
        finally:
957
1097
        finally:
958
1098
            tree1.unlock()
959
1099
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
960
 
        tree2 = MemoryTree.create_on_branch(branch2)
 
1100
        tree2 = memorytree.MemoryTree.create_on_branch(branch2)
961
1101
        tree2.lock_write()
962
1102
        try:
963
1103
            tree2.put_file_bytes_non_atomic('file-id', 'new file-content')
970
1110
            (('', '', root_id), [
971
1111
             ('d', '', 0, False, dirstate.DirState.NULLSTAT),
972
1112
             ('d', '', 0, False, revid1.encode('utf8')),
973
 
             ('d', '', 0, False, revid2.encode('utf8'))
 
1113
             ('d', '', 0, False, revid1.encode('utf8'))
974
1114
             ]),
975
1115
            (('', 'a file', 'file-id'), [
976
1116
             ('a', '', 0, False, ''),
1008
1148
            (('', '', 'TREE_ROOT'), [
1009
1149
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1010
1150
             ]),
1011
 
            (('', 'a file', 'a file id'), [
 
1151
            (('', 'a file', 'a-file-id'), [
1012
1152
             ('f', '1'*20, 19, False, dirstate.pack_stat(stat)), # current tree
1013
1153
             ]),
1014
1154
            ]
1015
1155
        try:
1016
 
            state.add('a file', 'a file id', 'file', stat, '1'*20)
 
1156
            state.add('a file', 'a-file-id', 'file', stat, '1'*20)
1017
1157
            # having added it, it should be in the output of iter_entries.
1018
1158
            self.assertEqual(expected_entries, list(state._iter_entries()))
1019
1159
            # saving and reloading should not affect this.
1022
1162
            state.unlock()
1023
1163
        state = dirstate.DirState.on_file('dirstate')
1024
1164
        state.lock_read()
1025
 
        try:
1026
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1027
 
        finally:
1028
 
            state.unlock()
 
1165
        self.addCleanup(state.unlock)
 
1166
        self.assertEqual(expected_entries, list(state._iter_entries()))
1029
1167
 
1030
1168
    def test_add_path_to_unversioned_directory(self):
1031
1169
        """Adding a path to an unversioned directory should error.
1036
1174
        """
1037
1175
        self.build_tree(['unversioned/', 'unversioned/a file'])
1038
1176
        state = dirstate.DirState.initialize('dirstate')
1039
 
        try:
1040
 
            self.assertRaises(errors.NotVersionedError, state.add,
1041
 
                'unversioned/a file', 'a file id', 'file', None, None)
1042
 
        finally:
1043
 
            state.unlock()
 
1177
        self.addCleanup(state.unlock)
 
1178
        self.assertRaises(errors.NotVersionedError, state.add,
 
1179
                          'unversioned/a file', 'a-file-id', 'file', None, None)
1044
1180
 
1045
1181
    def test_add_directory_to_root_no_parents_all_data(self):
1046
1182
        # The most trivial addition of a dir is when there are no parents and
1066
1202
            state.unlock()
1067
1203
        state = dirstate.DirState.on_file('dirstate')
1068
1204
        state.lock_read()
 
1205
        self.addCleanup(state.unlock)
1069
1206
        state._validate()
1070
 
        try:
1071
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1072
 
        finally:
1073
 
            state.unlock()
 
1207
        self.assertEqual(expected_entries, list(state._iter_entries()))
1074
1208
 
1075
 
    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):
1076
1210
        # The most trivial addition of a symlink when there are no parents and
1077
1211
        # its in the root and all data about the file is supplied
1078
1212
        # bzr doesn't support fake symlinks on windows, yet.
1079
 
        self.requireFeature(SymlinkFeature)
1080
 
        os.symlink('target', 'a link')
1081
 
        stat = os.lstat('a link')
 
1213
        self.requireFeature(features.SymlinkFeature)
 
1214
        os.symlink(target, link_name)
 
1215
        stat = os.lstat(link_name)
1082
1216
        expected_entries = [
1083
1217
            (('', '', 'TREE_ROOT'), [
1084
1218
             ('d', '', 0, False, dirstate.DirState.NULLSTAT), # current tree
1085
1219
             ]),
1086
 
            (('', 'a link', 'a link id'), [
1087
 
             ('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
1088
1223
             ]),
1089
1224
            ]
1090
1225
        state = dirstate.DirState.initialize('dirstate')
1091
1226
        try:
1092
 
            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'))
1093
1229
            # having added it, it should be in the output of iter_entries.
1094
1230
            self.assertEqual(expected_entries, list(state._iter_entries()))
1095
1231
            # saving and reloading should not affect this.
1098
1234
            state.unlock()
1099
1235
        state = dirstate.DirState.on_file('dirstate')
1100
1236
        state.lock_read()
1101
 
        try:
1102
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1103
 
        finally:
1104
 
            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')
1105
1247
 
1106
1248
    def test_add_directory_and_child_no_parents_all_data(self):
1107
1249
        # after adding a directory, we should be able to add children to it.
1115
1257
            (('', 'a dir', 'a dir id'), [
1116
1258
             ('d', '', 0, False, dirstate.pack_stat(dirstat)), # current tree
1117
1259
             ]),
1118
 
            (('a dir', 'a file', 'a file id'), [
 
1260
            (('a dir', 'a file', 'a-file-id'), [
1119
1261
             ('f', '1'*20, 25, False,
1120
1262
              dirstate.pack_stat(filestat)), # current tree details
1121
1263
             ]),
1123
1265
        state = dirstate.DirState.initialize('dirstate')
1124
1266
        try:
1125
1267
            state.add('a dir', 'a dir id', 'directory', dirstat, None)
1126
 
            state.add('a dir/a file', 'a file id', 'file', filestat, '1'*20)
 
1268
            state.add('a dir/a file', 'a-file-id', 'file', filestat, '1'*20)
1127
1269
            # added it, it should be in the output of iter_entries.
1128
1270
            self.assertEqual(expected_entries, list(state._iter_entries()))
1129
1271
            # saving and reloading should not affect this.
1132
1274
            state.unlock()
1133
1275
        state = dirstate.DirState.on_file('dirstate')
1134
1276
        state.lock_read()
1135
 
        try:
1136
 
            self.assertEqual(expected_entries, list(state._iter_entries()))
1137
 
        finally:
1138
 
            state.unlock()
 
1277
        self.addCleanup(state.unlock)
 
1278
        self.assertEqual(expected_entries, list(state._iter_entries()))
1139
1279
 
1140
1280
    def test_add_tree_reference(self):
1141
1281
        # make a dirstate and add a tree reference
1155
1295
            state.unlock()
1156
1296
        # now check we can read it back
1157
1297
        state.lock_read()
 
1298
        self.addCleanup(state.unlock)
1158
1299
        state._validate()
1159
 
        try:
1160
 
            entry2 = state._get_entry(0, 'subdir-id', 'subdir')
1161
 
            self.assertEqual(entry, entry2)
1162
 
            self.assertEqual(entry, expected_entry)
1163
 
            # and lookup by id should work too
1164
 
            entry2 = state._get_entry(0, fileid_utf8='subdir-id')
1165
 
            self.assertEqual(entry, expected_entry)
1166
 
        finally:
1167
 
            state.unlock()
 
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)
1168
1306
 
1169
1307
    def test_add_forbidden_names(self):
1170
1308
        state = dirstate.DirState.initialize('dirstate')
1174
1312
        self.assertRaises(errors.BzrError,
1175
1313
            state.add, '..', 'ass-id', 'directory', None, None)
1176
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
 
1177
1403
 
1178
1404
class TestGetLines(TestCaseWithDirState):
1179
1405
 
1412
1638
        There is one parent tree, which has the same shape with the following variations:
1413
1639
        b/g in the parent is gone.
1414
1640
        b/h in the parent has a different id
1415
 
        b/i is new in the parent 
 
1641
        b/i is new in the parent
1416
1642
        c is renamed to b/j in the parent
1417
1643
 
1418
1644
        :return: The dirstate, still write-locked.
1508
1734
            list(state._iter_child_entries(1, '')))
1509
1735
 
1510
1736
 
1511
 
class TestDirstateSortOrder(TestCaseWithTransport):
 
1737
class TestDirstateSortOrder(tests.TestCaseWithTransport):
1512
1738
    """Test that DirState adds entries in the right order."""
1513
1739
 
1514
1740
    def test_add_sorting(self):
1563
1789
 
1564
1790
        # *really* cheesy way to just get an empty tree
1565
1791
        repo = self.make_repository('repo')
1566
 
        empty_tree = repo.revision_tree(None)
 
1792
        empty_tree = repo.revision_tree(_mod_revision.NULL_REVISION)
1567
1793
        state.set_parent_trees([('null:', empty_tree)], [])
1568
1794
 
1569
1795
        dirblock_names = [d[0] for d in state._dirblocks]
1573
1799
class InstrumentedDirState(dirstate.DirState):
1574
1800
    """An DirState with instrumented sha1 functionality."""
1575
1801
 
1576
 
    def __init__(self, path):
1577
 
        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)
1578
1805
        self._time_offset = 0
1579
1806
        self._log = []
1580
1807
        # member is dynamically set in DirState.__init__ to turn on trace
 
1808
        self._sha1_provider = sha1_provider
1581
1809
        self._sha1_file = self._sha1_file_and_log
1582
1810
 
1583
1811
    def _sha_cutoff_time(self):
1586
1814
 
1587
1815
    def _sha1_file_and_log(self, abspath):
1588
1816
        self._log.append(('sha1', abspath))
1589
 
        return osutils.sha_file_by_name(abspath)
 
1817
        return self._sha1_provider.sha1(abspath)
1590
1818
 
1591
1819
    def _read_link(self, abspath, old_link):
1592
1820
        self._log.append(('read_link', abspath, old_link))
1623
1851
        self.st_ino = ino
1624
1852
        self.st_mode = mode
1625
1853
 
1626
 
 
1627
 
class TestUpdateEntry(TestCaseWithDirState):
1628
 
    """Test the DirState.update_entry functions"""
1629
 
 
1630
 
    def get_state_with_a(self):
1631
 
        """Create a DirState tracking a single object named 'a'"""
1632
 
        state = InstrumentedDirState.initialize('dirstate')
1633
 
        self.addCleanup(state.unlock)
1634
 
        state.add('a', 'a-id', 'file', None, '')
1635
 
        entry = state._get_entry(0, path_utf8='a')
1636
 
        return state, entry
1637
 
 
1638
 
    def test_update_entry(self):
1639
 
        state, entry = self.get_state_with_a()
1640
 
        self.build_tree(['a'])
1641
 
        # Add one where we don't provide the stat or sha already
1642
 
        self.assertEqual(('', 'a', 'a-id'), entry[0])
1643
 
        self.assertEqual([('f', '', 0, False, dirstate.DirState.NULLSTAT)],
1644
 
                         entry[1])
1645
 
        # Flush the buffers to disk
1646
 
        state.save()
1647
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1648
 
                         state._dirblock_state)
1649
 
 
1650
 
        stat_value = os.lstat('a')
1651
 
        packed_stat = dirstate.pack_stat(stat_value)
1652
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1653
 
                                          stat_value=stat_value)
1654
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1655
 
                         link_or_sha1)
1656
 
 
1657
 
        # The dirblock entry should not cache the file's sha1
1658
 
        self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1659
 
                         entry[1])
1660
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1661
 
                         state._dirblock_state)
1662
 
        mode = stat_value.st_mode
1663
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False)], state._log)
1664
 
 
1665
 
        state.save()
1666
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1667
 
                         state._dirblock_state)
1668
 
 
1669
 
        # If we do it again right away, we don't know if the file has changed
1670
 
        # so we will re-read the file. Roll the clock back so the file is
1671
 
        # guaranteed to look too new.
1672
 
        state.adjust_time(-10)
1673
 
 
1674
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1675
 
                                          stat_value=stat_value)
1676
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1677
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1678
 
                         ], state._log)
1679
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1680
 
                         link_or_sha1)
1681
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1682
 
                         state._dirblock_state)
1683
 
        self.assertEqual([('f', '', 14, False, dirstate.DirState.NULLSTAT)],
1684
 
                         entry[1])
1685
 
        state.save()
1686
 
 
1687
 
        # However, if we move the clock forward so the file is considered
1688
 
        # "stable", it should just cache the value.
1689
 
        state.adjust_time(+20)
1690
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1691
 
                                          stat_value=stat_value)
1692
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1693
 
                         link_or_sha1)
1694
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1695
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1696
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1697
 
                         ], state._log)
1698
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1699
 
                         entry[1])
1700
 
 
1701
 
        # Subsequent calls will just return the cached value
1702
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1703
 
                                          stat_value=stat_value)
1704
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1705
 
                         link_or_sha1)
1706
 
        self.assertEqual([('sha1', 'a'), ('is_exec', mode, False),
1707
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1708
 
                          ('sha1', 'a'), ('is_exec', mode, False),
1709
 
                         ], state._log)
1710
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1711
 
                         entry[1])
1712
 
 
1713
 
    def test_update_entry_symlink(self):
1714
 
        """Update entry should read symlinks."""
1715
 
        self.requireFeature(SymlinkFeature)
1716
 
        state, entry = self.get_state_with_a()
1717
 
        state.save()
1718
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1719
 
                         state._dirblock_state)
1720
 
        os.symlink('target', 'a')
1721
 
 
1722
 
        state.adjust_time(-10) # Make the symlink look new
1723
 
        stat_value = os.lstat('a')
1724
 
        packed_stat = dirstate.pack_stat(stat_value)
1725
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1726
 
                                          stat_value=stat_value)
1727
 
        self.assertEqual('target', link_or_sha1)
1728
 
        self.assertEqual([('read_link', 'a', '')], state._log)
1729
 
        # Dirblock is not updated (the link is too new)
1730
 
        self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1731
 
                         entry[1])
1732
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1733
 
                         state._dirblock_state)
1734
 
 
1735
 
        # Because the stat_value looks new, we should re-read the target
1736
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1737
 
                                          stat_value=stat_value)
1738
 
        self.assertEqual('target', link_or_sha1)
1739
 
        self.assertEqual([('read_link', 'a', ''),
1740
 
                          ('read_link', 'a', ''),
1741
 
                         ], state._log)
1742
 
        self.assertEqual([('l', '', 6, False, dirstate.DirState.NULLSTAT)],
1743
 
                         entry[1])
1744
 
        state.adjust_time(+20) # Skip into the future, all files look old
1745
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1746
 
                                          stat_value=stat_value)
1747
 
        self.assertEqual('target', link_or_sha1)
1748
 
        # We need to re-read the link because only now can we cache it
1749
 
        self.assertEqual([('read_link', 'a', ''),
1750
 
                          ('read_link', 'a', ''),
1751
 
                          ('read_link', 'a', ''),
1752
 
                         ], state._log)
1753
 
        self.assertEqual([('l', 'target', 6, False, packed_stat)],
1754
 
                         entry[1])
1755
 
 
1756
 
        # Another call won't re-read the link
1757
 
        self.assertEqual([('read_link', 'a', ''),
1758
 
                          ('read_link', 'a', ''),
1759
 
                          ('read_link', 'a', ''),
1760
 
                         ], state._log)
1761
 
        link_or_sha1 = state.update_entry(entry, abspath='a',
1762
 
                                          stat_value=stat_value)
1763
 
        self.assertEqual('target', link_or_sha1)
1764
 
        self.assertEqual([('l', 'target', 6, False, packed_stat)],
1765
 
                         entry[1])
1766
 
 
1767
 
    def do_update_entry(self, state, entry, abspath):
1768
 
        stat_value = os.lstat(abspath)
1769
 
        return state.update_entry(entry, abspath, stat_value)
1770
 
 
1771
 
    def test_update_entry_dir(self):
1772
 
        state, entry = self.get_state_with_a()
1773
 
        self.build_tree(['a/'])
1774
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1775
 
 
1776
 
    def test_update_entry_dir_unchanged(self):
1777
 
        state, entry = self.get_state_with_a()
1778
 
        self.build_tree(['a/'])
1779
 
        state.adjust_time(+20)
1780
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1781
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1782
 
                         state._dirblock_state)
1783
 
        state.save()
1784
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1785
 
                         state._dirblock_state)
1786
 
        self.assertIs(None, self.do_update_entry(state, entry, 'a'))
1787
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1788
 
                         state._dirblock_state)
1789
 
 
1790
 
    def test_update_entry_file_unchanged(self):
1791
 
        state, entry = self.get_state_with_a()
1792
 
        self.build_tree(['a'])
1793
 
        sha1sum = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1794
 
        state.adjust_time(+20)
1795
 
        self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1796
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
1797
 
                         state._dirblock_state)
1798
 
        state.save()
1799
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1800
 
                         state._dirblock_state)
1801
 
        self.assertEqual(sha1sum, self.do_update_entry(state, entry, 'a'))
1802
 
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
1803
 
                         state._dirblock_state)
1804
 
 
1805
 
    def create_and_test_file(self, state, entry):
1806
 
        """Create a file at 'a' and verify the state finds it.
1807
 
 
1808
 
        The state should already be versioning *something* at 'a'. This makes
1809
 
        sure that state.update_entry recognizes it as a file.
1810
 
        """
1811
 
        self.build_tree(['a'])
1812
 
        stat_value = os.lstat('a')
1813
 
        packed_stat = dirstate.pack_stat(stat_value)
1814
 
 
1815
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1816
 
        self.assertEqual('b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6',
1817
 
                         link_or_sha1)
1818
 
        self.assertEqual([('f', link_or_sha1, 14, False, packed_stat)],
1819
 
                         entry[1])
1820
 
        return packed_stat
1821
 
 
1822
 
    def create_and_test_dir(self, state, entry):
1823
 
        """Create a directory at 'a' and verify the state finds it.
1824
 
 
1825
 
        The state should already be versioning *something* at 'a'. This makes
1826
 
        sure that state.update_entry recognizes it as a directory.
1827
 
        """
1828
 
        self.build_tree(['a/'])
1829
 
        stat_value = os.lstat('a')
1830
 
        packed_stat = dirstate.pack_stat(stat_value)
1831
 
 
1832
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1833
 
        self.assertIs(None, link_or_sha1)
1834
 
        self.assertEqual([('d', '', 0, False, packed_stat)], entry[1])
1835
 
 
1836
 
        return packed_stat
1837
 
 
1838
 
    def create_and_test_symlink(self, state, entry):
1839
 
        """Create a symlink at 'a' and verify the state finds it.
1840
 
 
1841
 
        The state should already be versioning *something* at 'a'. This makes
1842
 
        sure that state.update_entry recognizes it as a symlink.
1843
 
 
1844
 
        This should not be called if this platform does not have symlink
1845
 
        support.
1846
 
        """
1847
 
        # caller should care about skipping test on platforms without symlinks
1848
 
        os.symlink('path/to/foo', 'a')
1849
 
 
1850
 
        stat_value = os.lstat('a')
1851
 
        packed_stat = dirstate.pack_stat(stat_value)
1852
 
 
1853
 
        link_or_sha1 = self.do_update_entry(state, entry, abspath='a')
1854
 
        self.assertEqual('path/to/foo', link_or_sha1)
1855
 
        self.assertEqual([('l', 'path/to/foo', 11, False, packed_stat)],
1856
 
                         entry[1])
1857
 
        return packed_stat
1858
 
 
1859
 
    def test_update_file_to_dir(self):
1860
 
        """If a file changes to a directory we return None for the sha.
1861
 
        We also update the inventory record.
1862
 
        """
1863
 
        state, entry = self.get_state_with_a()
1864
 
        # The file sha1 won't be cached unless the file is old
1865
 
        state.adjust_time(+10)
1866
 
        self.create_and_test_file(state, entry)
1867
 
        os.remove('a')
1868
 
        self.create_and_test_dir(state, entry)
1869
 
 
1870
 
    def test_update_file_to_symlink(self):
1871
 
        """File becomes a symlink"""
1872
 
        self.requireFeature(SymlinkFeature)
1873
 
        state, entry = self.get_state_with_a()
1874
 
        # The file sha1 won't be cached unless the file is old
1875
 
        state.adjust_time(+10)
1876
 
        self.create_and_test_file(state, entry)
1877
 
        os.remove('a')
1878
 
        self.create_and_test_symlink(state, entry)
1879
 
 
1880
 
    def test_update_dir_to_file(self):
1881
 
        """Directory becoming a file updates the entry."""
1882
 
        state, entry = self.get_state_with_a()
1883
 
        # The file sha1 won't be cached unless the file is old
1884
 
        state.adjust_time(+10)
1885
 
        self.create_and_test_dir(state, entry)
1886
 
        os.rmdir('a')
1887
 
        self.create_and_test_file(state, entry)
1888
 
 
1889
 
    def test_update_dir_to_symlink(self):
1890
 
        """Directory becomes a symlink"""
1891
 
        self.requireFeature(SymlinkFeature)
1892
 
        state, entry = self.get_state_with_a()
1893
 
        # The symlink target won't be cached if it isn't old
1894
 
        state.adjust_time(+10)
1895
 
        self.create_and_test_dir(state, entry)
1896
 
        os.rmdir('a')
1897
 
        self.create_and_test_symlink(state, entry)
1898
 
 
1899
 
    def test_update_symlink_to_file(self):
1900
 
        """Symlink becomes a file"""
1901
 
        self.requireFeature(SymlinkFeature)
1902
 
        state, entry = self.get_state_with_a()
1903
 
        # The symlink and file info won't be cached unless old
1904
 
        state.adjust_time(+10)
1905
 
        self.create_and_test_symlink(state, entry)
1906
 
        os.remove('a')
1907
 
        self.create_and_test_file(state, entry)
1908
 
 
1909
 
    def test_update_symlink_to_dir(self):
1910
 
        """Symlink becomes a directory"""
1911
 
        self.requireFeature(SymlinkFeature)
1912
 
        state, entry = self.get_state_with_a()
1913
 
        # The symlink target won't be cached if it isn't old
1914
 
        state.adjust_time(+10)
1915
 
        self.create_and_test_symlink(state, entry)
1916
 
        os.remove('a')
1917
 
        self.create_and_test_dir(state, entry)
1918
 
 
1919
 
    def test__is_executable_win32(self):
1920
 
        state, entry = self.get_state_with_a()
1921
 
        self.build_tree(['a'])
1922
 
 
1923
 
        # Make sure we are using the win32 implementation of _is_executable
1924
 
        state._is_executable = state._is_executable_win32
1925
 
 
1926
 
        # The file on disk is not executable, but we are marking it as though
1927
 
        # it is. With _is_executable_win32 we ignore what is on disk.
1928
 
        entry[1][0] = ('f', '', 0, True, dirstate.DirState.NULLSTAT)
1929
 
 
1930
 
        stat_value = os.lstat('a')
1931
 
        packed_stat = dirstate.pack_stat(stat_value)
1932
 
 
1933
 
        state.adjust_time(-10) # Make sure everything is new
1934
 
        state.update_entry(entry, abspath='a', stat_value=stat_value)
1935
 
 
1936
 
        # The row is updated, but the executable bit stays set.
1937
 
        self.assertEqual([('f', '', 14, True, dirstate.DirState.NULLSTAT)],
1938
 
                         entry[1])
1939
 
 
1940
 
        # Make the disk object look old enough to cache
1941
 
        state.adjust_time(+20)
1942
 
        digest = 'b50e5406bb5e153ebbeb20268fcf37c87e1ecfb6'
1943
 
        state.update_entry(entry, abspath='a', stat_value=stat_value)
1944
 
        self.assertEqual([('f', digest, 14, True, packed_stat)], entry[1])
1945
 
 
1946
 
 
1947
 
class TestPackStat(TestCaseWithTransport):
 
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):
1948
1861
 
1949
1862
    def assertPackStat(self, expected, stat_value):
1950
1863
        """Check the packed and serialized form of a stat value."""
2015
1928
        # the end it would still be fairly arbitrary, and we don't want the
2016
1929
        # extra overhead if we can avoid it. So sort everything to make sure
2017
1930
        # equality is true
2018
 
        assert len(map_keys) == len(paths)
 
1931
        self.assertEqual(len(map_keys), len(paths))
2019
1932
        expected = {}
2020
1933
        for path, keys in zip(paths, map_keys):
2021
1934
            if keys is None:
2040
1953
        :param paths: A list of directories
2041
1954
        """
2042
1955
        result = state._bisect_dirblocks(paths)
2043
 
        assert len(map_keys) == len(paths)
2044
 
 
 
1956
        self.assertEqual(len(map_keys), len(paths))
2045
1957
        expected = {}
2046
1958
        for path, keys in zip(paths, map_keys):
2047
1959
            if keys is None:
2296
2208
class TestDirstateTreeReference(TestCaseWithDirState):
2297
2209
 
2298
2210
    def test_reference_revision_is_none(self):
2299
 
        tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
 
2211
        tree = self.make_branch_and_tree('tree', format='development-subtree')
2300
2212
        subtree = self.make_branch_and_tree('tree/subtree',
2301
 
                            format='dirstate-with-subtree')
 
2213
                            format='development-subtree')
2302
2214
        subtree.set_root_id('subtree')
2303
2215
        tree.add_reference(subtree)
2304
2216
        tree.add('subtree')
2476
2388
        state._discard_merge_parents()
2477
2389
        state._validate()
2478
2390
        self.assertEqual(exp_dirblocks, state._dirblocks)
 
2391
 
 
2392
 
 
2393
class Test_InvEntryToDetails(tests.TestCase):
 
2394
 
 
2395
    def assertDetails(self, expected, inv_entry):
 
2396
        details = dirstate.DirState._inv_entry_to_details(inv_entry)
 
2397
        self.assertEqual(expected, details)
 
2398
        # details should always allow join() and always be a plain str when
 
2399
        # finished
 
2400
        (minikind, fingerprint, size, executable, tree_data) = details
 
2401
        self.assertIsInstance(minikind, str)
 
2402
        self.assertIsInstance(fingerprint, str)
 
2403
        self.assertIsInstance(tree_data, str)
 
2404
 
 
2405
    def test_unicode_symlink(self):
 
2406
        inv_entry = inventory.InventoryLink('link-file-id',
 
2407
                                            u'nam\N{Euro Sign}e',
 
2408
                                            'link-parent-id')
 
2409
        inv_entry.revision = 'link-revision-id'
 
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')])