~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Ian Clatworthy
  • Date: 2007-11-30 04:28:32 UTC
  • mto: (3054.1.1 ianc-integration)
  • mto: This revision was merged to the branch mainline in revision 3055.
  • Revision ID: ian.clatworthy@internode.on.net-20071130042832-6prruj0kzg3fodm8
chapter 2 tweaks

Show diffs side-by-side

added added

removed removed

Lines of Context:
19
19
import sys
20
20
 
21
21
from bzrlib import (
 
22
    errors,
 
23
    generate_ids,
 
24
    symbol_versioning,
22
25
    tests,
23
26
    urlutils,
24
27
    )
28
31
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
29
32
                           ReusingTransform, CantMoveRoot, 
30
33
                           PathsNotVersionedError, ExistingLimbo,
31
 
                           ImmortalLimbo, LockError)
32
 
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
 
34
                           ExistingPendingDeletion, ImmortalLimbo,
 
35
                           ImmortalPendingDeletion, LockError)
 
36
from bzrlib.osutils import file_kind, pathjoin
33
37
from bzrlib.merge import Merge3Merger
34
 
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
 
38
from bzrlib.tests import (
 
39
    SymlinkFeature,
 
40
    TestCase,
 
41
    TestCaseInTempDir,
 
42
    TestSkipped,
 
43
    )
35
44
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
36
45
                              resolve_conflicts, cook_conflicts, 
37
 
                              find_interesting, build_tree, get_backup_name)
38
 
 
39
 
 
40
 
class TestTreeTransform(TestCaseInTempDir):
 
46
                              find_interesting, build_tree, get_backup_name,
 
47
                              change_entry, _FileMover)
 
48
 
 
49
 
 
50
class TestTreeTransform(tests.TestCaseWithTransport):
41
51
 
42
52
    def setUp(self):
43
53
        super(TestTreeTransform, self).setUp()
44
 
        self.wt = BzrDir.create_standalone_workingtree('.')
 
54
        self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
45
55
        os.chdir('..')
46
56
 
47
57
    def get_transform(self):
48
58
        transform = TreeTransform(self.wt)
49
59
        #self.addCleanup(transform.finalize)
50
 
        return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
 
60
        return transform, transform.root
51
61
 
52
62
    def test_existing_limbo(self):
53
 
        limbo_name = urlutils.local_path_from_url(
54
 
            self.wt._control_files.controlfilename('limbo'))
55
63
        transform, root = self.get_transform()
 
64
        limbo_name = transform._limbodir
 
65
        deletion_path = transform._deletiondir
56
66
        os.mkdir(pathjoin(limbo_name, 'hehe'))
57
67
        self.assertRaises(ImmortalLimbo, transform.apply)
58
68
        self.assertRaises(LockError, self.wt.unlock)
60
70
        self.assertRaises(LockError, self.wt.unlock)
61
71
        os.rmdir(pathjoin(limbo_name, 'hehe'))
62
72
        os.rmdir(limbo_name)
 
73
        os.rmdir(deletion_path)
63
74
        transform, root = self.get_transform()
64
75
        transform.apply()
65
76
 
 
77
    def test_existing_pending_deletion(self):
 
78
        transform, root = self.get_transform()
 
79
        deletion_path = self._limbodir = urlutils.local_path_from_url(
 
80
            transform._tree._control_files.controlfilename('pending-deletion'))
 
81
        os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
 
82
        self.assertRaises(ImmortalPendingDeletion, transform.apply)
 
83
        self.assertRaises(LockError, self.wt.unlock)
 
84
        self.assertRaises(ExistingPendingDeletion, self.get_transform)
 
85
 
66
86
    def test_build(self):
67
87
        transform, root = self.get_transform() 
68
88
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
124
144
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
125
145
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
126
146
 
127
 
        self.assertEqual('toto-contents', 
 
147
        self.assertEqual('toto-contents',
128
148
                         self.wt.get_file_byname('oz/dorothy/toto').read())
129
149
        self.assertIs(self.wt.is_executable('toto-id'), False)
130
150
 
 
151
    def test_tree_reference(self):
 
152
        transform, root = self.get_transform()
 
153
        tree = transform._tree
 
154
        trans_id = transform.new_directory('reference', root, 'subtree-id')
 
155
        transform.set_tree_reference('subtree-revision', trans_id)
 
156
        transform.apply()
 
157
        tree.lock_read()
 
158
        self.addCleanup(tree.unlock)
 
159
        self.assertEqual('subtree-revision',
 
160
                         tree.inventory['subtree-id'].reference_revision)
 
161
 
131
162
    def test_conflicts(self):
132
163
        transform, root = self.get_transform()
133
164
        trans_id = transform.new_file('name', root, 'contents', 
197
228
        transform3.delete_contents(oz_id)
198
229
        self.assertEqual(transform3.find_conflicts(), 
199
230
                         [('missing parent', oz_id)])
200
 
        root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
 
231
        root_id = transform3.root
201
232
        tip_id = transform3.trans_id_tree_file_id('tip-id')
202
233
        transform3.adjust_path('tip', root_id, tip_id)
203
234
        transform3.apply()
229
260
    def test_name_invariants(self):
230
261
        create_tree, root = self.get_transform()
231
262
        # prepare tree
232
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
263
        root = create_tree.root
233
264
        create_tree.new_file('name1', root, 'hello1', 'name1')
234
265
        create_tree.new_file('name2', root, 'hello2', 'name2')
235
266
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
239
270
        create_tree.apply()
240
271
 
241
272
        mangle_tree,root = self.get_transform()
242
 
        root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
 
273
        root = mangle_tree.root
243
274
        #swap names
244
275
        name1 = mangle_tree.trans_id_tree_file_id('name1')
245
276
        name2 = mangle_tree.trans_id_tree_file_id('name2')
324
355
    def test_move_dangling_ie(self):
325
356
        create_tree, root = self.get_transform()
326
357
        # prepare tree
327
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
358
        root = create_tree.root
328
359
        create_tree.new_file('name1', root, 'hello1', 'name1')
329
360
        create_tree.apply()
330
361
        delete_contents, root = self.get_transform()
340
371
    def test_replace_dangling_ie(self):
341
372
        create_tree, root = self.get_transform()
342
373
        # prepare tree
343
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
374
        root = create_tree.root
344
375
        create_tree.new_file('name1', root, 'hello1', 'name1')
345
376
        create_tree.apply()
346
377
        delete_contents = TreeTransform(self.wt)
359
390
        replace.apply()
360
391
 
361
392
    def test_symlinks(self):
362
 
        if not has_symlinks():
363
 
            raise TestSkipped('Symlinks are not supported on this platform')
 
393
        self.requireFeature(SymlinkFeature)
364
394
        transform,root = self.get_transform()
365
395
        oz_id = transform.new_directory('oz', root, 'oz-id')
366
396
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
380
410
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
381
411
                         'wizard-target')
382
412
 
 
413
    def test_unable_create_symlink(self):
 
414
        def tt_helper():
 
415
            wt = self.make_branch_and_tree('.')
 
416
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
417
            try:
 
418
                tt.new_symlink('foo', tt.root, 'bar')
 
419
                tt.apply()
 
420
            finally:
 
421
                wt.unlock()
 
422
        os_symlink = getattr(os, 'symlink', None)
 
423
        os.symlink = None
 
424
        try:
 
425
            err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
 
426
            self.assertEquals(
 
427
                "Unable to create symlink 'foo' on this platform",
 
428
                str(err))
 
429
        finally:
 
430
            if os_symlink:
 
431
                os.symlink = os_symlink
383
432
 
384
433
    def get_conflicted(self):
385
434
        create,root = self.get_transform()
514
563
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
515
564
        create.new_file('uvfile', root, 'othertext')
516
565
        create.apply()
517
 
        self.assertEqual(find_interesting(wt, wt, ['vfile']),
518
 
                         set(['myfile-id']))
519
 
        self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
520
 
                          ['uvfile'])
 
566
        result = self.applyDeprecated(symbol_versioning.zero_fifteen,
 
567
            find_interesting, wt, wt, ['vfile'])
 
568
        self.assertEqual(result, set(['myfile-id']))
521
569
 
522
570
    def test_set_executability_order(self):
523
571
        """Ensure that executability behaves the same, no matter what order.
532
580
        wt = transform._tree
533
581
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
534
582
                           True)
535
 
        sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
 
583
        sac = transform.new_file('set_after_creation', root,
 
584
                                 'Set after creation', 'sac')
536
585
        transform.set_executability(True, sac)
537
 
        uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
 
586
        uws = transform.new_file('unset_without_set', root, 'Unset badly',
 
587
                                 'uws')
538
588
        self.assertRaises(KeyError, transform.set_executability, None, uws)
539
589
        transform.apply()
540
590
        self.assertTrue(wt.is_executable('soc'))
585
635
        bar1_abspath = self.wt.abspath('bar')
586
636
        self.assertEqual([bar1_abspath], stat_paths)
587
637
 
 
638
    def test_iter_changes(self):
 
639
        self.wt.set_root_id('eert_toor')
 
640
        transform, root = self.get_transform()
 
641
        transform.new_file('old', root, 'blah', 'id-1', True)
 
642
        transform.apply()
 
643
        transform, root = self.get_transform()
 
644
        try:
 
645
            self.assertEqual([], list(transform._iter_changes()))
 
646
            old = transform.trans_id_tree_file_id('id-1')
 
647
            transform.unversion_file(old)
 
648
            self.assertEqual([('id-1', ('old', None), False, (True, False),
 
649
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
650
                (True, True))], list(transform._iter_changes()))
 
651
            transform.new_directory('new', root, 'id-1')
 
652
            self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
 
653
                ('eert_toor', 'eert_toor'), ('old', 'new'),
 
654
                ('file', 'directory'),
 
655
                (True, False))], list(transform._iter_changes()))
 
656
        finally:
 
657
            transform.finalize()
 
658
 
 
659
    def test_iter_changes_new(self):
 
660
        self.wt.set_root_id('eert_toor')
 
661
        transform, root = self.get_transform()
 
662
        transform.new_file('old', root, 'blah')
 
663
        transform.apply()
 
664
        transform, root = self.get_transform()
 
665
        try:
 
666
            old = transform.trans_id_tree_path('old')
 
667
            transform.version_file('id-1', old)
 
668
            self.assertEqual([('id-1', (None, 'old'), False, (False, True),
 
669
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
670
                (False, False))], list(transform._iter_changes()))
 
671
        finally:
 
672
            transform.finalize()
 
673
 
 
674
    def test_iter_changes_modifications(self):
 
675
        self.wt.set_root_id('eert_toor')
 
676
        transform, root = self.get_transform()
 
677
        transform.new_file('old', root, 'blah', 'id-1')
 
678
        transform.new_file('new', root, 'blah')
 
679
        transform.new_directory('subdir', root, 'subdir-id')
 
680
        transform.apply()
 
681
        transform, root = self.get_transform()
 
682
        try:
 
683
            old = transform.trans_id_tree_path('old')
 
684
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
685
            new = transform.trans_id_tree_path('new')
 
686
            self.assertEqual([], list(transform._iter_changes()))
 
687
 
 
688
            #content deletion
 
689
            transform.delete_contents(old)
 
690
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
691
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
 
692
                (False, False))], list(transform._iter_changes()))
 
693
 
 
694
            #content change
 
695
            transform.create_file('blah', old)
 
696
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
697
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
698
                (False, False))], list(transform._iter_changes()))
 
699
            transform.cancel_deletion(old)
 
700
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
701
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
702
                (False, False))], list(transform._iter_changes()))
 
703
            transform.cancel_creation(old)
 
704
 
 
705
            # move file_id to a different file
 
706
            self.assertEqual([], list(transform._iter_changes()))
 
707
            transform.unversion_file(old)
 
708
            transform.version_file('id-1', new)
 
709
            transform.adjust_path('old', root, new)
 
710
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
711
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
712
                (False, False))], list(transform._iter_changes()))
 
713
            transform.cancel_versioning(new)
 
714
            transform._removed_id = set()
 
715
 
 
716
            #execute bit
 
717
            self.assertEqual([], list(transform._iter_changes()))
 
718
            transform.set_executability(True, old)
 
719
            self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
 
720
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
721
                (False, True))], list(transform._iter_changes()))
 
722
            transform.set_executability(None, old)
 
723
 
 
724
            # filename
 
725
            self.assertEqual([], list(transform._iter_changes()))
 
726
            transform.adjust_path('new', root, old)
 
727
            transform._new_parent = {}
 
728
            self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
 
729
                ('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
 
730
                (False, False))], list(transform._iter_changes()))
 
731
            transform._new_name = {}
 
732
 
 
733
            # parent directory
 
734
            self.assertEqual([], list(transform._iter_changes()))
 
735
            transform.adjust_path('new', subdir, old)
 
736
            transform._new_name = {}
 
737
            self.assertEqual([('id-1', ('old', 'subdir/old'), False,
 
738
                (True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
 
739
                ('file', 'file'), (False, False))],
 
740
                list(transform._iter_changes()))
 
741
            transform._new_path = {}
 
742
 
 
743
        finally:
 
744
            transform.finalize()
 
745
 
 
746
    def test_iter_changes_modified_bleed(self):
 
747
        self.wt.set_root_id('eert_toor')
 
748
        """Modified flag should not bleed from one change to another"""
 
749
        # unfortunately, we have no guarantee that file1 (which is modified)
 
750
        # will be applied before file2.  And if it's applied after file2, it
 
751
        # obviously can't bleed into file2's change output.  But for now, it
 
752
        # works.
 
753
        transform, root = self.get_transform()
 
754
        transform.new_file('file1', root, 'blah', 'id-1')
 
755
        transform.new_file('file2', root, 'blah', 'id-2')
 
756
        transform.apply()
 
757
        transform, root = self.get_transform()
 
758
        try:
 
759
            transform.delete_contents(transform.trans_id_file_id('id-1'))
 
760
            transform.set_executability(True,
 
761
            transform.trans_id_file_id('id-2'))
 
762
            self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
 
763
                ('eert_toor', 'eert_toor'), ('file1', u'file1'),
 
764
                ('file', None), (False, False)),
 
765
                ('id-2', (u'file2', u'file2'), False, (True, True),
 
766
                ('eert_toor', 'eert_toor'), ('file2', u'file2'),
 
767
                ('file', 'file'), (False, True))],
 
768
                list(transform._iter_changes()))
 
769
        finally:
 
770
            transform.finalize()
 
771
 
 
772
    def test_iter_changes_move_missing(self):
 
773
        """Test moving ids with no files around"""
 
774
        self.wt.set_root_id('toor_eert')
 
775
        # Need two steps because versioning a non-existant file is a conflict.
 
776
        transform, root = self.get_transform()
 
777
        transform.new_directory('floater', root, 'floater-id')
 
778
        transform.apply()
 
779
        transform, root = self.get_transform()
 
780
        transform.delete_contents(transform.trans_id_tree_path('floater'))
 
781
        transform.apply()
 
782
        transform, root = self.get_transform()
 
783
        floater = transform.trans_id_tree_path('floater')
 
784
        try:
 
785
            transform.adjust_path('flitter', root, floater)
 
786
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
 
787
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
 
788
            (None, None), (False, False))], list(transform._iter_changes()))
 
789
        finally:
 
790
            transform.finalize()
 
791
 
 
792
    def test_iter_changes_pointless(self):
 
793
        """Ensure that no-ops are not treated as modifications"""
 
794
        self.wt.set_root_id('eert_toor')
 
795
        transform, root = self.get_transform()
 
796
        transform.new_file('old', root, 'blah', 'id-1')
 
797
        transform.new_directory('subdir', root, 'subdir-id')
 
798
        transform.apply()
 
799
        transform, root = self.get_transform()
 
800
        try:
 
801
            old = transform.trans_id_tree_path('old')
 
802
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
803
            self.assertEqual([], list(transform._iter_changes()))
 
804
            transform.delete_contents(subdir)
 
805
            transform.create_directory(subdir)
 
806
            transform.set_executability(False, old)
 
807
            transform.unversion_file(old)
 
808
            transform.version_file('id-1', old)
 
809
            transform.adjust_path('old', root, old)
 
810
            self.assertEqual([], list(transform._iter_changes()))
 
811
        finally:
 
812
            transform.finalize()
 
813
 
 
814
    def test_rename_count(self):
 
815
        transform, root = self.get_transform()
 
816
        transform.new_file('name1', root, 'contents')
 
817
        self.assertEqual(transform.rename_count, 0)
 
818
        transform.apply()
 
819
        self.assertEqual(transform.rename_count, 1)
 
820
        transform2, root = self.get_transform()
 
821
        transform2.adjust_path('name2', root,
 
822
                               transform2.trans_id_tree_path('name1'))
 
823
        self.assertEqual(transform2.rename_count, 0)
 
824
        transform2.apply()
 
825
        self.assertEqual(transform2.rename_count, 2)
 
826
 
 
827
    def test_change_parent(self):
 
828
        """Ensure that after we change a parent, the results are still right.
 
829
 
 
830
        Renames and parent changes on pending transforms can happen as part
 
831
        of conflict resolution, and are explicitly permitted by the
 
832
        TreeTransform API.
 
833
 
 
834
        This test ensures they work correctly with the rename-avoidance
 
835
        optimization.
 
836
        """
 
837
        transform, root = self.get_transform()
 
838
        parent1 = transform.new_directory('parent1', root)
 
839
        child1 = transform.new_file('child1', parent1, 'contents')
 
840
        parent2 = transform.new_directory('parent2', root)
 
841
        transform.adjust_path('child1', parent2, child1)
 
842
        transform.apply()
 
843
        self.failIfExists(self.wt.abspath('parent1/child1'))
 
844
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
845
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
 
846
        # no rename for child1 (counting only renames during apply)
 
847
        self.failUnlessEqual(2, transform.rename_count)
 
848
 
 
849
    def test_cancel_parent(self):
 
850
        """Cancelling a parent doesn't cause deletion of a non-empty directory
 
851
 
 
852
        This is like the test_change_parent, except that we cancel the parent
 
853
        before adjusting the path.  The transform must detect that the
 
854
        directory is non-empty, and move children to safe locations.
 
855
        """
 
856
        transform, root = self.get_transform()
 
857
        parent1 = transform.new_directory('parent1', root)
 
858
        child1 = transform.new_file('child1', parent1, 'contents')
 
859
        child2 = transform.new_file('child2', parent1, 'contents')
 
860
        try:
 
861
            transform.cancel_creation(parent1)
 
862
        except OSError:
 
863
            self.fail('Failed to move child1 before deleting parent1')
 
864
        transform.cancel_creation(child2)
 
865
        transform.create_directory(parent1)
 
866
        try:
 
867
            transform.cancel_creation(parent1)
 
868
        # If the transform incorrectly believes that child2 is still in
 
869
        # parent1's limbo directory, it will try to rename it and fail
 
870
        # because was already moved by the first cancel_creation.
 
871
        except OSError:
 
872
            self.fail('Transform still thinks child2 is a child of parent1')
 
873
        parent2 = transform.new_directory('parent2', root)
 
874
        transform.adjust_path('child1', parent2, child1)
 
875
        transform.apply()
 
876
        self.failIfExists(self.wt.abspath('parent1'))
 
877
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
878
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
 
879
        self.failUnlessEqual(2, transform.rename_count)
 
880
 
 
881
    def test_adjust_and_cancel(self):
 
882
        """Make sure adjust_path keeps track of limbo children properly"""
 
883
        transform, root = self.get_transform()
 
884
        parent1 = transform.new_directory('parent1', root)
 
885
        child1 = transform.new_file('child1', parent1, 'contents')
 
886
        parent2 = transform.new_directory('parent2', root)
 
887
        transform.adjust_path('child1', parent2, child1)
 
888
        transform.cancel_creation(child1)
 
889
        try:
 
890
            transform.cancel_creation(parent1)
 
891
        # if the transform thinks child1 is still in parent1's limbo
 
892
        # directory, it will attempt to move it and fail.
 
893
        except OSError:
 
894
            self.fail('Transform still thinks child1 is a child of parent1')
 
895
        transform.finalize()
 
896
 
 
897
    def test_noname_contents(self):
 
898
        """TreeTransform should permit deferring naming files."""
 
899
        transform, root = self.get_transform()
 
900
        parent = transform.trans_id_file_id('parent-id')
 
901
        try:
 
902
            transform.create_directory(parent)
 
903
        except KeyError:
 
904
            self.fail("Can't handle contents with no name")
 
905
        transform.finalize()
 
906
 
 
907
    def test_noname_contents_nested(self):
 
908
        """TreeTransform should permit deferring naming files."""
 
909
        transform, root = self.get_transform()
 
910
        parent = transform.trans_id_file_id('parent-id')
 
911
        try:
 
912
            transform.create_directory(parent)
 
913
        except KeyError:
 
914
            self.fail("Can't handle contents with no name")
 
915
        child = transform.new_directory('child', parent)
 
916
        transform.adjust_path('parent', root, parent)
 
917
        transform.apply()
 
918
        self.failUnlessExists(self.wt.abspath('parent/child'))
 
919
        self.assertEqual(1, transform.rename_count)
 
920
 
 
921
    def test_reuse_name(self):
 
922
        """Avoid reusing the same limbo name for different files"""
 
923
        transform, root = self.get_transform()
 
924
        parent = transform.new_directory('parent', root)
 
925
        child1 = transform.new_directory('child', parent)
 
926
        try:
 
927
            child2 = transform.new_directory('child', parent)
 
928
        except OSError:
 
929
            self.fail('Tranform tried to use the same limbo name twice')
 
930
        transform.adjust_path('child2', parent, child2)
 
931
        transform.apply()
 
932
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
 
933
        # child2 is put into top-level limbo because child1 has already
 
934
        # claimed the direct limbo path when child2 is created.  There is no
 
935
        # advantage in renaming files once they're in top-level limbo, except
 
936
        # as part of apply.
 
937
        self.assertEqual(2, transform.rename_count)
 
938
 
 
939
    def test_reuse_when_first_moved(self):
 
940
        """Don't avoid direct paths when it is safe to use them"""
 
941
        transform, root = self.get_transform()
 
942
        parent = transform.new_directory('parent', root)
 
943
        child1 = transform.new_directory('child', parent)
 
944
        transform.adjust_path('child1', parent, child1)
 
945
        child2 = transform.new_directory('child', parent)
 
946
        transform.apply()
 
947
        # limbo/new-1 => parent
 
948
        self.assertEqual(1, transform.rename_count)
 
949
 
 
950
    def test_reuse_after_cancel(self):
 
951
        """Don't avoid direct paths when it is safe to use them"""
 
952
        transform, root = self.get_transform()
 
953
        parent2 = transform.new_directory('parent2', root)
 
954
        child1 = transform.new_directory('child1', parent2)
 
955
        transform.cancel_creation(parent2)
 
956
        transform.create_directory(parent2)
 
957
        child2 = transform.new_directory('child1', parent2)
 
958
        transform.adjust_path('child2', parent2, child1)
 
959
        transform.apply()
 
960
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
 
961
        self.assertEqual(2, transform.rename_count)
 
962
 
 
963
    def test_finalize_order(self):
 
964
        """Finalize must be done in child-to-parent order"""
 
965
        transform, root = self.get_transform()
 
966
        parent = transform.new_directory('parent', root)
 
967
        child = transform.new_directory('child', parent)
 
968
        try:
 
969
            transform.finalize()
 
970
        except OSError:
 
971
            self.fail('Tried to remove parent before child1')
 
972
 
 
973
    def test_cancel_with_cancelled_child_should_succeed(self):
 
974
        transform, root = self.get_transform()
 
975
        parent = transform.new_directory('parent', root)
 
976
        child = transform.new_directory('child', parent)
 
977
        transform.cancel_creation(child)
 
978
        transform.cancel_creation(parent)
 
979
        transform.finalize()
 
980
 
 
981
    def test_change_entry(self):
 
982
        txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
 
983
        self.callDeprecated([txt], change_entry, None, None, None, None, None,
 
984
            None, None, None)
 
985
 
588
986
 
589
987
class TransformGroup(object):
590
 
    def __init__(self, dirname):
 
988
    def __init__(self, dirname, root_id):
591
989
        self.name = dirname
592
990
        os.mkdir(dirname)
593
991
        self.wt = BzrDir.create_standalone_workingtree(dirname)
 
992
        self.wt.set_root_id(root_id)
594
993
        self.b = self.wt.branch
595
994
        self.tt = TreeTransform(self.wt)
596
995
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
597
996
 
 
997
 
598
998
def conflict_text(tree, merge):
599
999
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
600
1000
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
602
1002
 
603
1003
class TestTransformMerge(TestCaseInTempDir):
604
1004
    def test_text_merge(self):
605
 
        base = TransformGroup("base")
 
1005
        root_id = generate_ids.gen_root_id()
 
1006
        base = TransformGroup("base", root_id)
606
1007
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
607
1008
        base.tt.new_file('b', base.root, 'b1', 'b')
608
1009
        base.tt.new_file('c', base.root, 'c', 'c')
612
1013
        base.tt.new_directory('g', base.root, 'g')
613
1014
        base.tt.new_directory('h', base.root, 'h')
614
1015
        base.tt.apply()
615
 
        other = TransformGroup("other")
 
1016
        other = TransformGroup("other", root_id)
616
1017
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
617
1018
        other.tt.new_file('b', other.root, 'b2', 'b')
618
1019
        other.tt.new_file('c', other.root, 'c2', 'c')
623
1024
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
624
1025
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
625
1026
        other.tt.apply()
626
 
        this = TransformGroup("this")
 
1027
        this = TransformGroup("this", root_id)
627
1028
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
628
1029
        this.tt.new_file('b', this.root, 'b', 'b')
629
1030
        this.tt.new_file('c', this.root, 'c', 'c')
675
1076
        self.assertSubset(merge_modified, modified)
676
1077
        self.assertEqual(len(merge_modified), len(modified))
677
1078
        this.wt.remove('b')
678
 
        this.wt.revert([])
 
1079
        this.wt.revert()
679
1080
 
680
1081
    def test_file_merge(self):
681
 
        if not has_symlinks():
682
 
            raise TestSkipped('Symlinks are not supported on this platform')
683
 
        base = TransformGroup("BASE")
684
 
        this = TransformGroup("THIS")
685
 
        other = TransformGroup("OTHER")
 
1082
        self.requireFeature(SymlinkFeature)
 
1083
        root_id = generate_ids.gen_root_id()
 
1084
        base = TransformGroup("BASE", root_id)
 
1085
        this = TransformGroup("THIS", root_id)
 
1086
        other = TransformGroup("OTHER", root_id)
686
1087
        for tg in this, base, other:
687
1088
            tg.tt.new_directory('a', tg.root, 'a')
688
1089
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
720
1121
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
721
1122
 
722
1123
    def test_filename_merge(self):
723
 
        base = TransformGroup("BASE")
724
 
        this = TransformGroup("THIS")
725
 
        other = TransformGroup("OTHER")
 
1124
        root_id = generate_ids.gen_root_id()
 
1125
        base = TransformGroup("BASE", root_id)
 
1126
        this = TransformGroup("THIS", root_id)
 
1127
        other = TransformGroup("OTHER", root_id)
726
1128
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
727
1129
                                   for t in [base, this, other]]
728
1130
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
752
1154
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
753
1155
 
754
1156
    def test_filename_merge_conflicts(self):
755
 
        base = TransformGroup("BASE")
756
 
        this = TransformGroup("THIS")
757
 
        other = TransformGroup("OTHER")
 
1157
        root_id = generate_ids.gen_root_id()
 
1158
        base = TransformGroup("BASE", root_id)
 
1159
        this = TransformGroup("THIS", root_id)
 
1160
        other = TransformGroup("OTHER", root_id)
758
1161
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
759
1162
                                   for t in [base, this, other]]
760
1163
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
784
1187
 
785
1188
class TestBuildTree(tests.TestCaseWithTransport):
786
1189
 
787
 
    def test_build_tree(self):
788
 
        if not has_symlinks():
789
 
            raise TestSkipped('Test requires symlink support')
 
1190
    def test_build_tree_with_symlinks(self):
 
1191
        self.requireFeature(SymlinkFeature)
790
1192
        os.mkdir('a')
791
1193
        a = BzrDir.create_standalone_workingtree('a')
792
1194
        os.mkdir('a/foo')
795
1197
        a.add(['foo', 'foo/bar', 'foo/baz'])
796
1198
        a.commit('initial commit')
797
1199
        b = BzrDir.create_standalone_workingtree('b')
798
 
        build_tree(a.basis_tree(), b)
 
1200
        basis = a.basis_tree()
 
1201
        basis.lock_read()
 
1202
        self.addCleanup(basis.unlock)
 
1203
        build_tree(basis, b)
799
1204
        self.assertIs(os.path.isdir('b/foo'), True)
800
1205
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
801
1206
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
802
1207
 
 
1208
    def test_build_with_references(self):
 
1209
        tree = self.make_branch_and_tree('source',
 
1210
            format='dirstate-with-subtree')
 
1211
        subtree = self.make_branch_and_tree('source/subtree',
 
1212
            format='dirstate-with-subtree')
 
1213
        tree.add_reference(subtree)
 
1214
        tree.commit('a revision')
 
1215
        tree.branch.create_checkout('target')
 
1216
        self.failUnlessExists('target')
 
1217
        self.failUnlessExists('target/subtree')
 
1218
 
803
1219
    def test_file_conflict_handling(self):
804
1220
        """Ensure that when building trees, conflict handling is done"""
805
1221
        source = self.make_branch_and_tree('source')
826
1242
 
827
1243
    def test_symlink_conflict_handling(self):
828
1244
        """Ensure that when building trees, conflict handling is done"""
829
 
        if not has_symlinks():
830
 
            raise TestSkipped('Test requires symlink support')
 
1245
        self.requireFeature(SymlinkFeature)
831
1246
        source = self.make_branch_and_tree('source')
832
1247
        os.symlink('foo', 'source/symlink')
833
1248
        source.add('symlink', 'new-symlink')
904
1319
        target = self.make_branch_and_tree('target')
905
1320
        self.build_tree(['target/name'])
906
1321
        target.add('name')
907
 
        self.assertRaises(AssertionError, build_tree, source.basis_tree(),
908
 
                          target)
 
1322
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
 
1323
            build_tree, source.basis_tree(), target)
 
1324
 
 
1325
    def test_build_tree_rename_count(self):
 
1326
        source = self.make_branch_and_tree('source')
 
1327
        self.build_tree(['source/file1', 'source/dir1/'])
 
1328
        source.add(['file1', 'dir1'])
 
1329
        source.commit('add1')
 
1330
        target1 = self.make_branch_and_tree('target1')
 
1331
        transform_result = build_tree(source.basis_tree(), target1)
 
1332
        self.assertEqual(2, transform_result.rename_count)
 
1333
 
 
1334
        self.build_tree(['source/dir1/file2'])
 
1335
        source.add(['dir1/file2'])
 
1336
        source.commit('add3')
 
1337
        target2 = self.make_branch_and_tree('target2')
 
1338
        transform_result = build_tree(source.basis_tree(), target2)
 
1339
        # children of non-root directories should not be renamed
 
1340
        self.assertEqual(2, transform_result.rename_count)
909
1341
 
910
1342
 
911
1343
class MockTransform(object):
919
1351
                return True
920
1352
        return False
921
1353
 
 
1354
 
922
1355
class MockEntry(object):
923
1356
    def __init__(self):
924
1357
        object.__init__(self)
937
1370
        self.assertEqual(name, 'name.~1~')
938
1371
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
939
1372
        self.assertEqual(name, 'name.~4~')
 
1373
 
 
1374
 
 
1375
class TestFileMover(tests.TestCaseWithTransport):
 
1376
 
 
1377
    def test_file_mover(self):
 
1378
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
 
1379
        mover = _FileMover()
 
1380
        mover.rename('a', 'q')
 
1381
        self.failUnlessExists('q')
 
1382
        self.failIfExists('a')
 
1383
        self.failUnlessExists('q/b')
 
1384
        self.failUnlessExists('c')
 
1385
        self.failUnlessExists('c/d')
 
1386
 
 
1387
    def test_pre_delete_rollback(self):
 
1388
        self.build_tree(['a/'])
 
1389
        mover = _FileMover()
 
1390
        mover.pre_delete('a', 'q')
 
1391
        self.failUnlessExists('q')
 
1392
        self.failIfExists('a')
 
1393
        mover.rollback()
 
1394
        self.failIfExists('q')
 
1395
        self.failUnlessExists('a')
 
1396
 
 
1397
    def test_apply_deletions(self):
 
1398
        self.build_tree(['a/', 'b/'])
 
1399
        mover = _FileMover()
 
1400
        mover.pre_delete('a', 'q')
 
1401
        mover.pre_delete('b', 'r')
 
1402
        self.failUnlessExists('q')
 
1403
        self.failUnlessExists('r')
 
1404
        self.failIfExists('a')
 
1405
        self.failIfExists('b')
 
1406
        mover.apply_deletions()
 
1407
        self.failIfExists('q')
 
1408
        self.failIfExists('r')
 
1409
        self.failIfExists('a')
 
1410
        self.failIfExists('b')
 
1411
 
 
1412
    def test_file_mover_rollback(self):
 
1413
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
 
1414
        mover = _FileMover()
 
1415
        mover.rename('c/d', 'c/f')
 
1416
        mover.rename('c/e', 'c/d')
 
1417
        try:
 
1418
            mover.rename('a', 'c')
 
1419
        except OSError, e:
 
1420
            mover.rollback()
 
1421
        self.failUnlessExists('a')
 
1422
        self.failUnlessExists('c/d')
 
1423
 
 
1424
 
 
1425
class Bogus(Exception):
 
1426
    pass
 
1427
 
 
1428
 
 
1429
class TestTransformRollback(tests.TestCaseWithTransport):
 
1430
 
 
1431
    class ExceptionFileMover(_FileMover):
 
1432
 
 
1433
        def __init__(self, bad_source=None, bad_target=None):
 
1434
            _FileMover.__init__(self)
 
1435
            self.bad_source = bad_source
 
1436
            self.bad_target = bad_target
 
1437
 
 
1438
        def rename(self, source, target):
 
1439
            if (self.bad_source is not None and
 
1440
                source.endswith(self.bad_source)):
 
1441
                raise Bogus
 
1442
            elif (self.bad_target is not None and
 
1443
                target.endswith(self.bad_target)):
 
1444
                raise Bogus
 
1445
            else:
 
1446
                _FileMover.rename(self, source, target)
 
1447
 
 
1448
    def test_rollback_rename(self):
 
1449
        tree = self.make_branch_and_tree('.')
 
1450
        self.build_tree(['a/', 'a/b'])
 
1451
        tt = TreeTransform(tree)
 
1452
        self.addCleanup(tt.finalize)
 
1453
        a_id = tt.trans_id_tree_path('a')
 
1454
        tt.adjust_path('c', tt.root, a_id)
 
1455
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1456
        self.assertRaises(Bogus, tt.apply,
 
1457
                          _mover=self.ExceptionFileMover(bad_source='a'))
 
1458
        self.failUnlessExists('a')
 
1459
        self.failUnlessExists('a/b')
 
1460
        tt.apply()
 
1461
        self.failUnlessExists('c')
 
1462
        self.failUnlessExists('c/d')
 
1463
 
 
1464
    def test_rollback_rename_into_place(self):
 
1465
        tree = self.make_branch_and_tree('.')
 
1466
        self.build_tree(['a/', 'a/b'])
 
1467
        tt = TreeTransform(tree)
 
1468
        self.addCleanup(tt.finalize)
 
1469
        a_id = tt.trans_id_tree_path('a')
 
1470
        tt.adjust_path('c', tt.root, a_id)
 
1471
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1472
        self.assertRaises(Bogus, tt.apply,
 
1473
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
 
1474
        self.failUnlessExists('a')
 
1475
        self.failUnlessExists('a/b')
 
1476
        tt.apply()
 
1477
        self.failUnlessExists('c')
 
1478
        self.failUnlessExists('c/d')
 
1479
 
 
1480
    def test_rollback_deletion(self):
 
1481
        tree = self.make_branch_and_tree('.')
 
1482
        self.build_tree(['a/', 'a/b'])
 
1483
        tt = TreeTransform(tree)
 
1484
        self.addCleanup(tt.finalize)
 
1485
        a_id = tt.trans_id_tree_path('a')
 
1486
        tt.delete_contents(a_id)
 
1487
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
 
1488
        self.assertRaises(Bogus, tt.apply,
 
1489
                          _mover=self.ExceptionFileMover(bad_target='d'))
 
1490
        self.failUnlessExists('a')
 
1491
        self.failUnlessExists('a/b')
 
1492
 
 
1493
    def test_resolve_no_parent(self):
 
1494
        wt = self.make_branch_and_tree('.')
 
1495
        tt = TreeTransform(wt)
 
1496
        self.addCleanup(tt.finalize)
 
1497
        parent = tt.trans_id_file_id('parent-id')
 
1498
        tt.new_file('file', parent, 'Contents')
 
1499
        resolve_conflicts(tt)