/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to breezy/tests/test_dirstate.py

  • Committer: Jelmer Vernooij
  • Date: 2017-07-10 23:47:19 UTC
  • mto: This revision was merged to the branch mainline in revision 6732.
  • Revision ID: jelmer@jelmer.uk-20170710234719-6gec6320uvchcslm
Move lazy regex error to breezy.lazy_regex.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006-2010 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
16
16
 
17
17
"""Tests of the dirstate functionality being built for WorkingTreeFormat4."""
18
18
 
19
 
import bisect
20
19
import os
 
20
import tempfile
21
21
 
22
 
from bzrlib import (
23
 
    dirstate,
 
22
from .. import (
 
23
    controldir,
24
24
    errors,
25
 
    inventory,
26
25
    memorytree,
27
26
    osutils,
28
27
    revision as _mod_revision,
 
28
    revisiontree,
29
29
    tests,
30
30
    )
31
 
from bzrlib.tests import test_osutils
 
31
from ..bzr import (
 
32
    dirstate,
 
33
    inventory,
 
34
    inventorytree,
 
35
    workingtree_4,
 
36
    )
 
37
from . import (
 
38
    features,
 
39
    test_osutils,
 
40
    )
 
41
from .scenarios import load_tests_apply_scenarios
32
42
 
33
43
 
34
44
# TODO:
44
54
# set_path_id  setting id when state is in memory modified
45
55
 
46
56
 
47
 
def load_tests(basic_tests, module, loader):
48
 
    suite = loader.suiteClass()
49
 
    dir_reader_tests, remaining_tests = tests.split_suite_by_condition(
50
 
        basic_tests, tests.condition_isinstance(TestCaseWithDirState))
51
 
    tests.multiply_tests(dir_reader_tests,
52
 
                         test_osutils.dir_reader_scenarios(), suite)
53
 
    suite.addTest(remaining_tests)
54
 
    return suite
 
57
load_tests = load_tests_apply_scenarios
55
58
 
56
59
 
57
60
class TestCaseWithDirState(tests.TestCaseWithTransport):
58
61
    """Helper functions for creating DirState objects with various content."""
59
62
 
 
63
    scenarios = test_osutils.dir_reader_scenarios()
 
64
 
60
65
    # Set by load_tests
61
66
    _dir_reader_class = None
62
67
    _native_to_unicode = None # Not used yet
63
68
 
64
69
    def setUp(self):
65
 
        tests.TestCaseWithTransport.setUp(self)
66
 
 
 
70
        super(TestCaseWithDirState, self).setUp()
67
71
        self.overrideAttr(osutils,
68
72
                          '_selected_dir_reader', self._dir_reader_class())
69
73
 
406
410
        # create a parent by doing a commit
407
411
        tree = self.make_branch_and_tree('tree')
408
412
        rev_id = tree.commit('first post')
409
 
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
413
        tree2 = tree.controldir.sprout('tree2').open_workingtree()
410
414
        rev_id2 = tree2.commit('second post', allow_pointless=True)
411
415
        tree.merge_from_branch(tree2.branch)
412
416
        expected_result = ([rev_id, rev_id2], [
481
485
        # create a parent by doing a commit
482
486
        tree = self.get_tree_with_a_file()
483
487
        rev_id = tree.commit('first post').encode('utf8')
484
 
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
488
        tree2 = tree.controldir.sprout('tree2').open_workingtree()
485
489
        # change the current content to be different this will alter stat, sha
486
490
        # and length:
487
491
        self.build_tree_contents([('tree2/a file', 'merge content\n')])
532
536
 
533
537
class TestDirStateOnFile(TestCaseWithDirState):
534
538
 
 
539
    def create_updated_dirstate(self):
 
540
        self.build_tree(['a-file'])
 
541
        tree = self.make_branch_and_tree('.')
 
542
        tree.add(['a-file'], ['a-id'])
 
543
        tree.commit('add a-file')
 
544
        # Save and unlock the state, re-open it in readonly mode
 
545
        state = dirstate.DirState.from_tree(tree, 'dirstate')
 
546
        state.save()
 
547
        state.unlock()
 
548
        state = dirstate.DirState.on_file('dirstate')
 
549
        state.lock_read()
 
550
        return state
 
551
 
535
552
    def test_construct_with_path(self):
536
553
        tree = self.make_branch_and_tree('tree')
537
554
        state = dirstate.DirState.from_tree(tree, 'dirstate.from_tree')
566
583
            state.unlock()
567
584
 
568
585
    def test_can_save_in_read_lock(self):
569
 
        self.build_tree(['a-file'])
570
 
        state = dirstate.DirState.initialize('dirstate')
571
 
        try:
572
 
            # No stat and no sha1 sum.
573
 
            state.add('a-file', 'a-file-id', 'file', None, '')
574
 
            state.save()
575
 
        finally:
576
 
            state.unlock()
577
 
 
578
 
        # Now open in readonly mode
579
 
        state = dirstate.DirState.on_file('dirstate')
580
 
        state.lock_read()
 
586
        state = self.create_updated_dirstate()
581
587
        try:
582
588
            entry = state._get_entry(0, path_utf8='a-file')
583
589
            # The current size should be 0 (default)
584
590
            self.assertEqual(0, entry[1][0][2])
585
591
            # We should have a real entry.
586
592
            self.assertNotEqual((None, None), entry)
587
 
            # Make sure everything is old enough
 
593
            # Set the cutoff-time into the future, so things look cacheable
588
594
            state._sha_cutoff_time()
589
 
            state._cutoff_time += 10
590
 
            # Change the file length
591
 
            self.build_tree_contents([('a-file', 'shorter')])
592
 
            sha1sum = dirstate.update_entry(state, entry, 'a-file',
593
 
                os.lstat('a-file'))
594
 
            # new file, no cached sha:
595
 
            self.assertEqual(None, sha1sum)
 
595
            state._cutoff_time += 10.0
 
596
            st = os.lstat('a-file')
 
597
            sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
 
598
            # We updated the current sha1sum because the file is cacheable
 
599
            self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
 
600
                             sha1sum)
596
601
 
597
602
            # The dirblock has been updated
598
 
            self.assertEqual(7, entry[1][0][2])
599
 
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
603
            self.assertEqual(st.st_size, entry[1][0][2])
 
604
            self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
600
605
                             state._dirblock_state)
601
606
 
602
607
            del entry
611
616
        state.lock_read()
612
617
        try:
613
618
            entry = state._get_entry(0, path_utf8='a-file')
614
 
            self.assertEqual(7, entry[1][0][2])
 
619
            self.assertEqual(st.st_size, entry[1][0][2])
615
620
        finally:
616
621
            state.unlock()
617
622
 
618
623
    def test_save_fails_quietly_if_locked(self):
619
624
        """If dirstate is locked, save will fail without complaining."""
620
 
        self.build_tree(['a-file'])
621
 
        state = dirstate.DirState.initialize('dirstate')
622
 
        try:
623
 
            # No stat and no sha1 sum.
624
 
            state.add('a-file', 'a-file-id', 'file', None, '')
625
 
            state.save()
626
 
        finally:
627
 
            state.unlock()
628
 
 
629
 
        state = dirstate.DirState.on_file('dirstate')
630
 
        state.lock_read()
 
625
        state = self.create_updated_dirstate()
631
626
        try:
632
627
            entry = state._get_entry(0, path_utf8='a-file')
633
 
            sha1sum = dirstate.update_entry(state, entry, 'a-file',
634
 
                os.lstat('a-file'))
635
 
            # No sha - too new
636
 
            self.assertEqual(None, sha1sum)
637
 
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
628
            # No cached sha1 yet.
 
629
            self.assertEqual('', entry[1][0][1])
 
630
            # Set the cutoff-time into the future, so things look cacheable
 
631
            state._sha_cutoff_time()
 
632
            state._cutoff_time += 10.0
 
633
            st = os.lstat('a-file')
 
634
            sha1sum = dirstate.update_entry(state, entry, 'a-file', st)
 
635
            self.assertEqual('ecc5374e9ed82ad3ea3b4d452ea995a5fd3e70e3',
 
636
                             sha1sum)
 
637
            self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
638
638
                             state._dirblock_state)
639
639
 
640
640
            # Now, before we try to save, grab another dirstate, and take out a
730
730
 
731
731
class TestDirStateManipulations(TestCaseWithDirState):
732
732
 
 
733
    def make_minimal_tree(self):
 
734
        tree1 = self.make_branch_and_memory_tree('tree1')
 
735
        tree1.lock_write()
 
736
        self.addCleanup(tree1.unlock)
 
737
        tree1.add('')
 
738
        revid1 = tree1.commit('foo')
 
739
        return tree1, revid1
 
740
 
 
741
    def test_update_minimal_updates_id_index(self):
 
742
        state = self.create_dirstate_with_root_and_subdir()
 
743
        self.addCleanup(state.unlock)
 
744
        id_index = state._get_id_index()
 
745
        self.assertEqual(['a-root-value', 'subdir-id'], sorted(id_index))
 
746
        state.add('file-name', 'file-id', 'file', None, '')
 
747
        self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
 
748
                         sorted(id_index))
 
749
        state.update_minimal(('', 'new-name', 'file-id'), 'f',
 
750
                             path_utf8='new-name')
 
751
        self.assertEqual(['a-root-value', 'file-id', 'subdir-id'],
 
752
                         sorted(id_index))
 
753
        self.assertEqual([('', 'new-name', 'file-id')],
 
754
                         sorted(id_index['file-id']))
 
755
        state._validate()
 
756
 
733
757
    def test_set_state_from_inventory_no_content_no_parents(self):
734
758
        # setting the current inventory is a slow but important api to support.
735
 
        tree1 = self.make_branch_and_memory_tree('tree1')
736
 
        tree1.lock_write()
737
 
        try:
738
 
            tree1.add('')
739
 
            revid1 = tree1.commit('foo').encode('utf8')
740
 
            root_id = tree1.get_root_id()
741
 
            inv = tree1.inventory
742
 
        finally:
743
 
            tree1.unlock()
 
759
        tree1, revid1 = self.make_minimal_tree()
 
760
        inv = tree1.root_inventory
 
761
        root_id = inv.path2id('')
744
762
        expected_result = [], [
745
763
            (('', '', root_id), [
746
764
             ('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
758
776
            # This will unlock it
759
777
            self.check_state_with_reopen(expected_result, state)
760
778
 
 
779
    def test_set_state_from_scratch_no_parents(self):
 
780
        tree1, revid1 = self.make_minimal_tree()
 
781
        inv = tree1.root_inventory
 
782
        root_id = inv.path2id('')
 
783
        expected_result = [], [
 
784
            (('', '', root_id), [
 
785
             ('d', '', 0, False, dirstate.DirState.NULLSTAT)])]
 
786
        state = dirstate.DirState.initialize('dirstate')
 
787
        try:
 
788
            state.set_state_from_scratch(inv, [], [])
 
789
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
790
                             state._header_state)
 
791
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
792
                             state._dirblock_state)
 
793
        except:
 
794
            state.unlock()
 
795
            raise
 
796
        else:
 
797
            # This will unlock it
 
798
            self.check_state_with_reopen(expected_result, state)
 
799
 
 
800
    def test_set_state_from_scratch_identical_parent(self):
 
801
        tree1, revid1 = self.make_minimal_tree()
 
802
        inv = tree1.root_inventory
 
803
        root_id = inv.path2id('')
 
804
        rev_tree1 = tree1.branch.repository.revision_tree(revid1)
 
805
        d_entry = ('d', '', 0, False, dirstate.DirState.NULLSTAT)
 
806
        parent_entry = ('d', '', 0, False, revid1)
 
807
        expected_result = [revid1], [
 
808
            (('', '', root_id), [d_entry, parent_entry])]
 
809
        state = dirstate.DirState.initialize('dirstate')
 
810
        try:
 
811
            state.set_state_from_scratch(inv, [(revid1, rev_tree1)], [])
 
812
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
813
                             state._header_state)
 
814
            self.assertEqual(dirstate.DirState.IN_MEMORY_MODIFIED,
 
815
                             state._dirblock_state)
 
816
        except:
 
817
            state.unlock()
 
818
            raise
 
819
        else:
 
820
            # This will unlock it
 
821
            self.check_state_with_reopen(expected_result, state)
 
822
 
761
823
    def test_set_state_from_inventory_preserves_hashcache(self):
762
824
        # https://bugs.launchpad.net/bzr/+bug/146176
763
825
        # set_state_from_inventory should preserve the stat and hash value for
795
857
                tree._dirstate._get_entry(0, 'foo-id'))
796
858
 
797
859
            # extract the inventory, and add something to it
798
 
            inv = tree._get_inventory()
 
860
            inv = tree._get_root_inventory()
799
861
            # should see the file we poked in...
800
862
            self.assertTrue(inv.has_id('foo-id'))
801
863
            self.assertTrue(inv.has_filename('foo'))
831
893
                      ['a-id', 'b-id', 'a-b-id', 'foo-id', 'bar-id'])
832
894
            tree1.commit('rev1', rev_id='rev1')
833
895
            root_id = tree1.get_root_id()
834
 
            inv = tree1.inventory
 
896
            inv = tree1.root_inventory
835
897
        finally:
836
898
            tree1.unlock()
837
899
        expected_result1 = [('', '', root_id, 'd'),
967
1029
            revid1 = tree1.commit('foo')
968
1030
        finally:
969
1031
            tree1.unlock()
970
 
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
 
1032
        branch2 = tree1.branch.controldir.clone('tree2').open_branch()
971
1033
        tree2 = memorytree.MemoryTree.create_on_branch(branch2)
972
1034
        tree2.lock_write()
973
1035
        try:
1037
1099
            revid1 = tree1.commit('foo')
1038
1100
        finally:
1039
1101
            tree1.unlock()
1040
 
        branch2 = tree1.branch.bzrdir.clone('tree2').open_branch()
 
1102
        branch2 = tree1.branch.controldir.clone('tree2').open_branch()
1041
1103
        tree2 = memorytree.MemoryTree.create_on_branch(branch2)
1042
1104
        tree2.lock_write()
1043
1105
        try:
1151
1213
        # The most trivial addition of a symlink when there are no parents and
1152
1214
        # its in the root and all data about the file is supplied
1153
1215
        # bzr doesn't support fake symlinks on windows, yet.
1154
 
        self.requireFeature(tests.SymlinkFeature)
 
1216
        self.requireFeature(features.SymlinkFeature)
1155
1217
        os.symlink(target, link_name)
1156
1218
        stat = os.lstat(link_name)
1157
1219
        expected_entries = [
1182
1244
        self._test_add_symlink_to_root_no_parents_all_data('a link', 'target')
1183
1245
 
1184
1246
    def test_add_symlink_unicode_to_root_no_parents_all_data(self):
1185
 
        self.requireFeature(tests.UnicodeFilenameFeature)
 
1247
        self.requireFeature(features.UnicodeFilenameFeature)
1186
1248
        self._test_add_symlink_to_root_no_parents_all_data(
1187
1249
            u'\N{Euro Sign}link', u'targ\N{Euro Sign}et')
1188
1250
 
1265
1327
        try:
1266
1328
            tree1.add(['b'], ['b-id'])
1267
1329
            root_id = tree1.get_root_id()
1268
 
            inv = tree1.inventory
 
1330
            inv = tree1.root_inventory
1269
1331
            state = dirstate.DirState.initialize('dirstate')
1270
1332
            try:
1271
1333
                # Set the initial state with 'b'
1286
1348
            tree1.unlock()
1287
1349
 
1288
1350
 
 
1351
class TestDirStateHashUpdates(TestCaseWithDirState):
 
1352
 
 
1353
    def do_update_entry(self, state, path):
 
1354
        entry = state._get_entry(0, path_utf8=path)
 
1355
        stat = os.lstat(path)
 
1356
        return dirstate.update_entry(state, entry, os.path.abspath(path), stat)
 
1357
 
 
1358
    def _read_state_content(self, state):
 
1359
        """Read the content of the dirstate file.
 
1360
 
 
1361
        On Windows when one process locks a file, you can't even open() the
 
1362
        file in another process (to read it). So we go directly to
 
1363
        state._state_file. This should always be the exact disk representation,
 
1364
        so it is reasonable to do so.
 
1365
        DirState also always seeks before reading, so it doesn't matter if we
 
1366
        bump the file pointer.
 
1367
        """
 
1368
        state._state_file.seek(0)
 
1369
        return state._state_file.read()
 
1370
 
 
1371
    def test_worth_saving_limit_avoids_writing(self):
 
1372
        tree = self.make_branch_and_tree('.')
 
1373
        self.build_tree(['c', 'd'])
 
1374
        tree.lock_write()
 
1375
        tree.add(['c', 'd'], ['c-id', 'd-id'])
 
1376
        tree.commit('add c and d')
 
1377
        state = InstrumentedDirState.on_file(tree.current_dirstate()._filename,
 
1378
                                             worth_saving_limit=2)
 
1379
        tree.unlock()
 
1380
        state.lock_write()
 
1381
        self.addCleanup(state.unlock)
 
1382
        state._read_dirblocks_if_needed()
 
1383
        state.adjust_time(+20) # Allow things to be cached
 
1384
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
1385
                         state._dirblock_state)
 
1386
        content = self._read_state_content(state)
 
1387
        self.do_update_entry(state, 'c')
 
1388
        self.assertEqual(1, len(state._known_hash_changes))
 
1389
        self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
 
1390
                         state._dirblock_state)
 
1391
        state.save()
 
1392
        # It should not have set the state to IN_MEMORY_UNMODIFIED because the
 
1393
        # hash values haven't been written out.
 
1394
        self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
 
1395
                         state._dirblock_state)
 
1396
        self.assertEqual(content, self._read_state_content(state))
 
1397
        self.assertEqual(dirstate.DirState.IN_MEMORY_HASH_MODIFIED,
 
1398
                         state._dirblock_state)
 
1399
        self.do_update_entry(state, 'd')
 
1400
        self.assertEqual(2, len(state._known_hash_changes))
 
1401
        state.save()
 
1402
        self.assertEqual(dirstate.DirState.IN_MEMORY_UNMODIFIED,
 
1403
                         state._dirblock_state)
 
1404
        self.assertEqual(0, len(state._known_hash_changes))
 
1405
 
 
1406
 
1289
1407
class TestGetLines(TestCaseWithDirState):
1290
1408
 
1291
1409
    def test_get_line_with_2_rows(self):
1684
1802
class InstrumentedDirState(dirstate.DirState):
1685
1803
    """An DirState with instrumented sha1 functionality."""
1686
1804
 
1687
 
    def __init__(self, path, sha1_provider):
1688
 
        super(InstrumentedDirState, self).__init__(path, sha1_provider)
 
1805
    def __init__(self, path, sha1_provider, worth_saving_limit=0):
 
1806
        super(InstrumentedDirState, self).__init__(path, sha1_provider,
 
1807
            worth_saving_limit=worth_saving_limit)
1689
1808
        self._time_offset = 0
1690
1809
        self._log = []
1691
1810
        # member is dynamically set in DirState.__init__ to turn on trace
1748
1867
        self.assertEqual(expected, dirstate.pack_stat(stat_value))
1749
1868
 
1750
1869
    def test_pack_stat_int(self):
1751
 
        st = _FakeStat(6859L, 1172758614, 1172758617, 777L, 6499538L, 0100644)
 
1870
        st = _FakeStat(6859, 1172758614, 1172758617, 777, 6499538, 0o100644)
1752
1871
        # Make sure that all parameters have an impact on the packed stat.
1753
1872
        self.assertPackStat('AAAay0Xm4FZF5uBZAAADCQBjLNIAAIGk', st)
1754
 
        st.st_size = 7000L
 
1873
        st.st_size = 7000
1755
1874
        #                ay0 => bWE
1756
1875
        self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1757
1876
        st.st_mtime = 1172758620
1760
1879
        st.st_ctime = 1172758630
1761
1880
        #                          uBZ => uBm
1762
1881
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADCQBjLNIAAIGk', st)
1763
 
        st.st_dev = 888L
 
1882
        st.st_dev = 888
1764
1883
        #                                DCQ => DeA
1765
1884
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNIAAIGk', st)
1766
 
        st.st_ino = 6499540L
 
1885
        st.st_ino = 6499540
1767
1886
        #                                     LNI => LNQ
1768
1887
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIGk', st)
1769
 
        st.st_mode = 0100744
 
1888
        st.st_mode = 0o100744
1770
1889
        #                                          IGk => IHk
1771
1890
        self.assertPackStat('AAAbWEXm4FxF5uBmAAADeABjLNQAAIHk', st)
1772
1891
 
1776
1895
        Make sure we don't get warnings or errors, and that we ignore changes <
1777
1896
        1s
1778
1897
        """
1779
 
        st = _FakeStat(7000L, 1172758614.0, 1172758617.0,
1780
 
                       777L, 6499538L, 0100644)
 
1898
        st = _FakeStat(7000, 1172758614.0, 1172758617.0,
 
1899
                       777, 6499538, 0o100644)
1781
1900
        # These should all be the same as the integer counterparts
1782
1901
        self.assertPackStat('AAAbWEXm4FZF5uBZAAADCQBjLNIAAIGk', st)
1783
1902
        st.st_mtime = 1172758620.0
2092
2211
class TestDirstateTreeReference(TestCaseWithDirState):
2093
2212
 
2094
2213
    def test_reference_revision_is_none(self):
2095
 
        tree = self.make_branch_and_tree('tree', format='dirstate-with-subtree')
 
2214
        tree = self.make_branch_and_tree('tree', format='development-subtree')
2096
2215
        subtree = self.make_branch_and_tree('tree/subtree',
2097
 
                            format='dirstate-with-subtree')
 
2216
                            format='development-subtree')
2098
2217
        subtree.set_root_id('subtree')
2099
2218
        tree.add_reference(subtree)
2100
2219
        tree.add('subtree')
2320
2439
        self.assertTrue(len(statvalue) >= 10)
2321
2440
        self.assertEqual(len(text), statvalue.st_size)
2322
2441
        self.assertEqual(expected_sha, sha1)
 
2442
 
 
2443
 
 
2444
class _Repo(object):
 
2445
    """A minimal api to get InventoryRevisionTree to work."""
 
2446
 
 
2447
    def __init__(self):
 
2448
        default_format = controldir.format_registry.make_controldir('default')
 
2449
        self._format = default_format.repository_format
 
2450
 
 
2451
    def lock_read(self):
 
2452
        pass
 
2453
 
 
2454
    def unlock(self):
 
2455
        pass
 
2456
 
 
2457
 
 
2458
class TestUpdateBasisByDelta(tests.TestCase):
 
2459
 
 
2460
    def path_to_ie(self, path, file_id, rev_id, dir_ids):
 
2461
        if path.endswith('/'):
 
2462
            is_dir = True
 
2463
            path = path[:-1]
 
2464
        else:
 
2465
            is_dir = False
 
2466
        dirname, basename = osutils.split(path)
 
2467
        try:
 
2468
            dir_id = dir_ids[dirname]
 
2469
        except KeyError:
 
2470
            dir_id = osutils.basename(dirname) + '-id'
 
2471
        if is_dir:
 
2472
            ie = inventory.InventoryDirectory(file_id, basename, dir_id)
 
2473
            dir_ids[path] = file_id
 
2474
        else:
 
2475
            ie = inventory.InventoryFile(file_id, basename, dir_id)
 
2476
            ie.text_size = 0
 
2477
            ie.text_sha1 = ''
 
2478
        ie.revision = rev_id
 
2479
        return ie
 
2480
 
 
2481
    def create_tree_from_shape(self, rev_id, shape):
 
2482
        dir_ids = {'': 'root-id'}
 
2483
        inv = inventory.Inventory('root-id', rev_id)
 
2484
        for info in shape:
 
2485
            if len(info) == 2:
 
2486
                path, file_id = info
 
2487
                ie_rev_id = rev_id
 
2488
            else:
 
2489
                path, file_id, ie_rev_id = info
 
2490
            if path == '':
 
2491
                # Replace the root entry
 
2492
                del inv._byid[inv.root.file_id]
 
2493
                inv.root.file_id = file_id
 
2494
                inv._byid[file_id] = inv.root
 
2495
                dir_ids[''] = file_id
 
2496
                continue
 
2497
            inv.add(self.path_to_ie(path, file_id, ie_rev_id, dir_ids))
 
2498
        return inventorytree.InventoryRevisionTree(_Repo(), inv, rev_id)
 
2499
 
 
2500
    def create_empty_dirstate(self):
 
2501
        fd, path = tempfile.mkstemp(prefix='bzr-dirstate')
 
2502
        self.addCleanup(os.remove, path)
 
2503
        os.close(fd)
 
2504
        state = dirstate.DirState.initialize(path)
 
2505
        self.addCleanup(state.unlock)
 
2506
        return state
 
2507
 
 
2508
    def create_inv_delta(self, delta, rev_id):
 
2509
        """Translate a 'delta shape' into an actual InventoryDelta"""
 
2510
        dir_ids = {'': 'root-id'}
 
2511
        inv_delta = []
 
2512
        for old_path, new_path, file_id in delta:
 
2513
            if old_path is not None and old_path.endswith('/'):
 
2514
                # Don't have to actually do anything for this, because only
 
2515
                # new_path creates InventoryEntries
 
2516
                old_path = old_path[:-1]
 
2517
            if new_path is None: # Delete
 
2518
                inv_delta.append((old_path, None, file_id, None))
 
2519
                continue
 
2520
            ie = self.path_to_ie(new_path, file_id, rev_id, dir_ids)
 
2521
            inv_delta.append((old_path, new_path, file_id, ie))
 
2522
        return inv_delta
 
2523
 
 
2524
    def assertUpdate(self, active, basis, target):
 
2525
        """Assert that update_basis_by_delta works how we want.
 
2526
 
 
2527
        Set up a DirState object with active_shape for tree 0, basis_shape for
 
2528
        tree 1. Then apply the delta from basis_shape to target_shape,
 
2529
        and assert that the DirState is still valid, and that its stored
 
2530
        content matches the target_shape.
 
2531
        """
 
2532
        active_tree = self.create_tree_from_shape('active', active)
 
2533
        basis_tree = self.create_tree_from_shape('basis', basis)
 
2534
        target_tree = self.create_tree_from_shape('target', target)
 
2535
        state = self.create_empty_dirstate()
 
2536
        state.set_state_from_scratch(active_tree.root_inventory,
 
2537
            [('basis', basis_tree)], [])
 
2538
        delta = target_tree.root_inventory._make_delta(
 
2539
            basis_tree.root_inventory)
 
2540
        state.update_basis_by_delta(delta, 'target')
 
2541
        state._validate()
 
2542
        dirstate_tree = workingtree_4.DirStateRevisionTree(state,
 
2543
            'target', _Repo())
 
2544
        # The target now that delta has been applied should match the
 
2545
        # RevisionTree
 
2546
        self.assertEqual([], list(dirstate_tree.iter_changes(target_tree)))
 
2547
        # And the dirblock state should be identical to the state if we created
 
2548
        # it from scratch.
 
2549
        state2 = self.create_empty_dirstate()
 
2550
        state2.set_state_from_scratch(active_tree.root_inventory,
 
2551
            [('target', target_tree)], [])
 
2552
        self.assertEqual(state2._dirblocks, state._dirblocks)
 
2553
        return state
 
2554
 
 
2555
    def assertBadDelta(self, active, basis, delta):
 
2556
        """Test that we raise InconsistentDelta when appropriate.
 
2557
 
 
2558
        :param active: The active tree shape
 
2559
        :param basis: The basis tree shape
 
2560
        :param delta: A description of the delta to apply. Similar to the form
 
2561
            for regular inventory deltas, but omitting the InventoryEntry.
 
2562
            So adding a file is: (None, 'path', 'file-id')
 
2563
            Adding a directory is: (None, 'path/', 'dir-id')
 
2564
            Renaming a dir is: ('old/', 'new/', 'dir-id')
 
2565
            etc.
 
2566
        """
 
2567
        active_tree = self.create_tree_from_shape('active', active)
 
2568
        basis_tree = self.create_tree_from_shape('basis', basis)
 
2569
        inv_delta = self.create_inv_delta(delta, 'target')
 
2570
        state = self.create_empty_dirstate()
 
2571
        state.set_state_from_scratch(active_tree.root_inventory,
 
2572
            [('basis', basis_tree)], [])
 
2573
        self.assertRaises(errors.InconsistentDelta,
 
2574
            state.update_basis_by_delta, inv_delta, 'target')
 
2575
        ## try:
 
2576
        ##     state.update_basis_by_delta(inv_delta, 'target')
 
2577
        ## except errors.InconsistentDelta, e:
 
2578
        ##     import pdb; pdb.set_trace()
 
2579
        ## else:
 
2580
        ##     import pdb; pdb.set_trace()
 
2581
        self.assertTrue(state._changes_aborted)
 
2582
 
 
2583
    def test_remove_file_matching_active_state(self):
 
2584
        state = self.assertUpdate(
 
2585
            active=[],
 
2586
            basis =[('file', 'file-id')],
 
2587
            target=[],
 
2588
            )
 
2589
 
 
2590
    def test_remove_file_present_in_active_state(self):
 
2591
        state = self.assertUpdate(
 
2592
            active=[('file', 'file-id')],
 
2593
            basis =[('file', 'file-id')],
 
2594
            target=[],
 
2595
            )
 
2596
 
 
2597
    def test_remove_file_present_elsewhere_in_active_state(self):
 
2598
        state = self.assertUpdate(
 
2599
            active=[('other-file', 'file-id')],
 
2600
            basis =[('file', 'file-id')],
 
2601
            target=[],
 
2602
            )
 
2603
 
 
2604
    def test_remove_file_active_state_has_diff_file(self):
 
2605
        state = self.assertUpdate(
 
2606
            active=[('file', 'file-id-2')],
 
2607
            basis =[('file', 'file-id')],
 
2608
            target=[],
 
2609
            )
 
2610
 
 
2611
    def test_remove_file_active_state_has_diff_file_and_file_elsewhere(self):
 
2612
        state = self.assertUpdate(
 
2613
            active=[('file', 'file-id-2'),
 
2614
                    ('other-file', 'file-id')],
 
2615
            basis =[('file', 'file-id')],
 
2616
            target=[],
 
2617
            )
 
2618
 
 
2619
    def test_add_file_matching_active_state(self):
 
2620
        state = self.assertUpdate(
 
2621
            active=[('file', 'file-id')],
 
2622
            basis =[],
 
2623
            target=[('file', 'file-id')],
 
2624
            )
 
2625
 
 
2626
    def test_add_file_in_empty_dir_not_matching_active_state(self):
 
2627
        state = self.assertUpdate(
 
2628
                active=[],
 
2629
                basis=[('dir/', 'dir-id')],
 
2630
                target=[('dir/', 'dir-id', 'basis'), ('dir/file', 'file-id')],
 
2631
                )
 
2632
 
 
2633
    def test_add_file_missing_in_active_state(self):
 
2634
        state = self.assertUpdate(
 
2635
            active=[],
 
2636
            basis =[],
 
2637
            target=[('file', 'file-id')],
 
2638
            )
 
2639
 
 
2640
    def test_add_file_elsewhere_in_active_state(self):
 
2641
        state = self.assertUpdate(
 
2642
            active=[('other-file', 'file-id')],
 
2643
            basis =[],
 
2644
            target=[('file', 'file-id')],
 
2645
            )
 
2646
 
 
2647
    def test_add_file_active_state_has_diff_file_and_file_elsewhere(self):
 
2648
        state = self.assertUpdate(
 
2649
            active=[('other-file', 'file-id'),
 
2650
                    ('file', 'file-id-2')],
 
2651
            basis =[],
 
2652
            target=[('file', 'file-id')],
 
2653
            )
 
2654
 
 
2655
    def test_rename_file_matching_active_state(self):
 
2656
        state = self.assertUpdate(
 
2657
            active=[('other-file', 'file-id')],
 
2658
            basis =[('file', 'file-id')],
 
2659
            target=[('other-file', 'file-id')],
 
2660
            )
 
2661
 
 
2662
    def test_rename_file_missing_in_active_state(self):
 
2663
        state = self.assertUpdate(
 
2664
            active=[],
 
2665
            basis =[('file', 'file-id')],
 
2666
            target=[('other-file', 'file-id')],
 
2667
            )
 
2668
 
 
2669
    def test_rename_file_present_elsewhere_in_active_state(self):
 
2670
        state = self.assertUpdate(
 
2671
            active=[('third', 'file-id')],
 
2672
            basis =[('file', 'file-id')],
 
2673
            target=[('other-file', 'file-id')],
 
2674
            )
 
2675
 
 
2676
    def test_rename_file_active_state_has_diff_source_file(self):
 
2677
        state = self.assertUpdate(
 
2678
            active=[('file', 'file-id-2')],
 
2679
            basis =[('file', 'file-id')],
 
2680
            target=[('other-file', 'file-id')],
 
2681
            )
 
2682
 
 
2683
    def test_rename_file_active_state_has_diff_target_file(self):
 
2684
        state = self.assertUpdate(
 
2685
            active=[('other-file', 'file-id-2')],
 
2686
            basis =[('file', 'file-id')],
 
2687
            target=[('other-file', 'file-id')],
 
2688
            )
 
2689
 
 
2690
    def test_rename_file_active_has_swapped_files(self):
 
2691
        state = self.assertUpdate(
 
2692
            active=[('file', 'file-id'),
 
2693
                    ('other-file', 'file-id-2')],
 
2694
            basis= [('file', 'file-id'),
 
2695
                    ('other-file', 'file-id-2')],
 
2696
            target=[('file', 'file-id-2'),
 
2697
                    ('other-file', 'file-id')])
 
2698
 
 
2699
    def test_rename_file_basis_has_swapped_files(self):
 
2700
        state = self.assertUpdate(
 
2701
            active=[('file', 'file-id'),
 
2702
                    ('other-file', 'file-id-2')],
 
2703
            basis= [('file', 'file-id-2'),
 
2704
                    ('other-file', 'file-id')],
 
2705
            target=[('file', 'file-id'),
 
2706
                    ('other-file', 'file-id-2')])
 
2707
 
 
2708
    def test_rename_directory_with_contents(self):
 
2709
        state = self.assertUpdate( # active matches basis
 
2710
            active=[('dir1/', 'dir-id'),
 
2711
                    ('dir1/file', 'file-id')],
 
2712
            basis= [('dir1/', 'dir-id'),
 
2713
                    ('dir1/file', 'file-id')],
 
2714
            target=[('dir2/', 'dir-id'),
 
2715
                    ('dir2/file', 'file-id')])
 
2716
        state = self.assertUpdate( # active matches target
 
2717
            active=[('dir2/', 'dir-id'),
 
2718
                    ('dir2/file', 'file-id')],
 
2719
            basis= [('dir1/', 'dir-id'),
 
2720
                    ('dir1/file', 'file-id')],
 
2721
            target=[('dir2/', 'dir-id'),
 
2722
                    ('dir2/file', 'file-id')])
 
2723
        state = self.assertUpdate( # active empty
 
2724
            active=[],
 
2725
            basis= [('dir1/', 'dir-id'),
 
2726
                    ('dir1/file', 'file-id')],
 
2727
            target=[('dir2/', 'dir-id'),
 
2728
                    ('dir2/file', 'file-id')])
 
2729
        state = self.assertUpdate( # active present at other location
 
2730
            active=[('dir3/', 'dir-id'),
 
2731
                    ('dir3/file', 'file-id')],
 
2732
            basis= [('dir1/', 'dir-id'),
 
2733
                    ('dir1/file', 'file-id')],
 
2734
            target=[('dir2/', 'dir-id'),
 
2735
                    ('dir2/file', 'file-id')])
 
2736
        state = self.assertUpdate( # active has different ids
 
2737
            active=[('dir1/', 'dir1-id'),
 
2738
                    ('dir1/file', 'file1-id'),
 
2739
                    ('dir2/', 'dir2-id'),
 
2740
                    ('dir2/file', 'file2-id')],
 
2741
            basis= [('dir1/', 'dir-id'),
 
2742
                    ('dir1/file', 'file-id')],
 
2743
            target=[('dir2/', 'dir-id'),
 
2744
                    ('dir2/file', 'file-id')])
 
2745
 
 
2746
    def test_invalid_file_not_present(self):
 
2747
        state = self.assertBadDelta(
 
2748
            active=[('file', 'file-id')],
 
2749
            basis= [('file', 'file-id')],
 
2750
            delta=[('other-file', 'file', 'file-id')])
 
2751
 
 
2752
    def test_invalid_new_id_same_path(self):
 
2753
        # The bad entry comes after
 
2754
        state = self.assertBadDelta(
 
2755
            active=[('file', 'file-id')],
 
2756
            basis= [('file', 'file-id')],
 
2757
            delta=[(None, 'file', 'file-id-2')])
 
2758
        # The bad entry comes first
 
2759
        state = self.assertBadDelta(
 
2760
            active=[('file', 'file-id-2')],
 
2761
            basis=[('file', 'file-id-2')],
 
2762
            delta=[(None, 'file', 'file-id')])
 
2763
 
 
2764
    def test_invalid_existing_id(self):
 
2765
        state = self.assertBadDelta(
 
2766
            active=[('file', 'file-id')],
 
2767
            basis= [('file', 'file-id')],
 
2768
            delta=[(None, 'file', 'file-id')])
 
2769
 
 
2770
    def test_invalid_parent_missing(self):
 
2771
        state = self.assertBadDelta(
 
2772
            active=[],
 
2773
            basis= [],
 
2774
            delta=[(None, 'path/path2', 'file-id')])
 
2775
        # Note: we force the active tree to have the directory, by knowing how
 
2776
        #       path_to_ie handles entries with missing parents
 
2777
        state = self.assertBadDelta(
 
2778
            active=[('path/', 'path-id')],
 
2779
            basis= [],
 
2780
            delta=[(None, 'path/path2', 'file-id')])
 
2781
        state = self.assertBadDelta(
 
2782
            active=[('path/', 'path-id'),
 
2783
                    ('path/path2', 'file-id')],
 
2784
            basis= [],
 
2785
            delta=[(None, 'path/path2', 'file-id')])
 
2786
 
 
2787
    def test_renamed_dir_same_path(self):
 
2788
        # We replace the parent directory, with another parent dir. But the C
 
2789
        # file doesn't look like it has been moved.
 
2790
        state = self.assertUpdate(# Same as basis
 
2791
            active=[('dir/', 'A-id'),
 
2792
                    ('dir/B', 'B-id')],
 
2793
            basis= [('dir/', 'A-id'),
 
2794
                    ('dir/B', 'B-id')],
 
2795
            target=[('dir/', 'C-id'),
 
2796
                    ('dir/B', 'B-id')])
 
2797
        state = self.assertUpdate(# Same as target
 
2798
            active=[('dir/', 'C-id'),
 
2799
                    ('dir/B', 'B-id')],
 
2800
            basis= [('dir/', 'A-id'),
 
2801
                    ('dir/B', 'B-id')],
 
2802
            target=[('dir/', 'C-id'),
 
2803
                    ('dir/B', 'B-id')])
 
2804
        state = self.assertUpdate(# empty active
 
2805
            active=[],
 
2806
            basis= [('dir/', 'A-id'),
 
2807
                    ('dir/B', 'B-id')],
 
2808
            target=[('dir/', 'C-id'),
 
2809
                    ('dir/B', 'B-id')])
 
2810
        state = self.assertUpdate(# different active
 
2811
            active=[('dir/', 'D-id'),
 
2812
                    ('dir/B', 'B-id')],
 
2813
            basis= [('dir/', 'A-id'),
 
2814
                    ('dir/B', 'B-id')],
 
2815
            target=[('dir/', 'C-id'),
 
2816
                    ('dir/B', 'B-id')])
 
2817
 
 
2818
    def test_parent_child_swap(self):
 
2819
        state = self.assertUpdate(# Same as basis
 
2820
            active=[('A/', 'A-id'),
 
2821
                    ('A/B/', 'B-id'),
 
2822
                    ('A/B/C', 'C-id')],
 
2823
            basis= [('A/', 'A-id'),
 
2824
                    ('A/B/', 'B-id'),
 
2825
                    ('A/B/C', 'C-id')],
 
2826
            target=[('A/', 'B-id'),
 
2827
                    ('A/B/', 'A-id'),
 
2828
                    ('A/B/C', 'C-id')])
 
2829
        state = self.assertUpdate(# Same as target
 
2830
            active=[('A/', 'B-id'),
 
2831
                    ('A/B/', 'A-id'),
 
2832
                    ('A/B/C', 'C-id')],
 
2833
            basis= [('A/', 'A-id'),
 
2834
                    ('A/B/', 'B-id'),
 
2835
                    ('A/B/C', 'C-id')],
 
2836
            target=[('A/', 'B-id'),
 
2837
                    ('A/B/', 'A-id'),
 
2838
                    ('A/B/C', 'C-id')])
 
2839
        state = self.assertUpdate(# empty active
 
2840
            active=[],
 
2841
            basis= [('A/', 'A-id'),
 
2842
                    ('A/B/', 'B-id'),
 
2843
                    ('A/B/C', 'C-id')],
 
2844
            target=[('A/', 'B-id'),
 
2845
                    ('A/B/', 'A-id'),
 
2846
                    ('A/B/C', 'C-id')])
 
2847
        state = self.assertUpdate(# different active
 
2848
            active=[('D/', 'A-id'),
 
2849
                    ('D/E/', 'B-id'),
 
2850
                    ('F', 'C-id')],
 
2851
            basis= [('A/', 'A-id'),
 
2852
                    ('A/B/', 'B-id'),
 
2853
                    ('A/B/C', 'C-id')],
 
2854
            target=[('A/', 'B-id'),
 
2855
                    ('A/B/', 'A-id'),
 
2856
                    ('A/B/C', 'C-id')])
 
2857
 
 
2858
    def test_change_root_id(self):
 
2859
        state = self.assertUpdate( # same as basis
 
2860
            active=[('', 'root-id'),
 
2861
                    ('file', 'file-id')],
 
2862
            basis= [('', 'root-id'),
 
2863
                    ('file', 'file-id')],
 
2864
            target=[('', 'target-root-id'),
 
2865
                    ('file', 'file-id')])
 
2866
        state = self.assertUpdate( # same as target
 
2867
            active=[('', 'target-root-id'),
 
2868
                    ('file', 'file-id')],
 
2869
            basis= [('', 'root-id'),
 
2870
                    ('file', 'file-id')],
 
2871
            target=[('', 'target-root-id'),
 
2872
                    ('file', 'root-id')])
 
2873
        state = self.assertUpdate( # all different
 
2874
            active=[('', 'active-root-id'),
 
2875
                    ('file', 'file-id')],
 
2876
            basis= [('', 'root-id'),
 
2877
                    ('file', 'file-id')],
 
2878
            target=[('', 'target-root-id'),
 
2879
                    ('file', 'root-id')])
 
2880
 
 
2881
    def test_change_file_absent_in_active(self):
 
2882
        state = self.assertUpdate(
 
2883
            active=[],
 
2884
            basis= [('file', 'file-id')],
 
2885
            target=[('file', 'file-id')])
 
2886
 
 
2887
    def test_invalid_changed_file(self):
 
2888
        state = self.assertBadDelta( # Not present in basis
 
2889
            active=[('file', 'file-id')],
 
2890
            basis= [],
 
2891
            delta=[('file', 'file', 'file-id')])
 
2892
        state = self.assertBadDelta( # present at another location in basis
 
2893
            active=[('file', 'file-id')],
 
2894
            basis= [('other-file', 'file-id')],
 
2895
            delta=[('file', 'file', 'file-id')])