/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-06-10 18:34:12 UTC
  • mto: This revision was merged to the branch mainline in revision 6690.
  • Revision ID: jelmer@jelmer.uk-20170610183412-s9fro6la0e1848x6
More moves.

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