/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 bzrlib/tests/test_transform.py

Merge bzr.dev

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2006 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
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import os
 
18
import stat
 
19
import sys
18
20
 
 
21
from bzrlib import (
 
22
    tests,
 
23
    urlutils,
 
24
    )
19
25
from bzrlib.bzrdir import BzrDir
20
26
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
21
 
                              UnversionedParent, ParentLoop)
 
27
                              UnversionedParent, ParentLoop, DeletingParent,)
22
28
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
23
 
                           ReusingTransform, CantMoveRoot, NotVersionedError,
24
 
                           ExistingLimbo, ImmortalLimbo, LockError)
 
29
                           ReusingTransform, CantMoveRoot, 
 
30
                           PathsNotVersionedError, ExistingLimbo,
 
31
                           ImmortalLimbo, LockError)
25
32
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
26
33
from bzrlib.merge import Merge3Merger
27
34
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
28
35
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
29
36
                              resolve_conflicts, cook_conflicts, 
30
37
                              find_interesting, build_tree, get_backup_name)
 
38
from bzrlib.workingtree import gen_root_id
 
39
 
31
40
 
32
41
class TestTreeTransform(TestCaseInTempDir):
 
42
 
33
43
    def setUp(self):
34
44
        super(TestTreeTransform, self).setUp()
35
45
        self.wt = BzrDir.create_standalone_workingtree('.')
38
48
    def get_transform(self):
39
49
        transform = TreeTransform(self.wt)
40
50
        #self.addCleanup(transform.finalize)
41
 
        return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
 
51
        return transform, transform.root
42
52
 
43
53
    def test_existing_limbo(self):
44
 
        limbo_name = self.wt._control_files.controlfilename('limbo')
 
54
        limbo_name = urlutils.local_path_from_url(
 
55
            self.wt._control_files.controlfilename('limbo'))
45
56
        transform, root = self.get_transform()
46
57
        os.mkdir(pathjoin(limbo_name, 'hehe'))
47
58
        self.assertRaises(ImmortalLimbo, transform.apply)
57
68
        transform, root = self.get_transform() 
58
69
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
59
70
        imaginary_id = transform.trans_id_tree_path('imaginary')
 
71
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
 
72
        self.assertEqual(imaginary_id, imaginary_id2)
60
73
        self.assertEqual(transform.get_tree_parent(imaginary_id), root)
61
74
        self.assertEqual(transform.final_kind(root), 'directory')
62
75
        self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
185
198
        transform3.delete_contents(oz_id)
186
199
        self.assertEqual(transform3.find_conflicts(), 
187
200
                         [('missing parent', oz_id)])
188
 
        root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
 
201
        root_id = transform3.root
189
202
        tip_id = transform3.trans_id_tree_file_id('tip-id')
190
203
        transform3.adjust_path('tip', root_id, tip_id)
191
204
        transform3.apply()
217
230
    def test_name_invariants(self):
218
231
        create_tree, root = self.get_transform()
219
232
        # prepare tree
220
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
233
        root = create_tree.root
221
234
        create_tree.new_file('name1', root, 'hello1', 'name1')
222
235
        create_tree.new_file('name2', root, 'hello2', 'name2')
223
236
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
227
240
        create_tree.apply()
228
241
 
229
242
        mangle_tree,root = self.get_transform()
230
 
        root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
 
243
        root = mangle_tree.root
231
244
        #swap names
232
245
        name1 = mangle_tree.trans_id_tree_file_id('name1')
233
246
        name2 = mangle_tree.trans_id_tree_file_id('name2')
312
325
    def test_move_dangling_ie(self):
313
326
        create_tree, root = self.get_transform()
314
327
        # prepare tree
315
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
328
        root = create_tree.root
316
329
        create_tree.new_file('name1', root, 'hello1', 'name1')
317
330
        create_tree.apply()
318
331
        delete_contents, root = self.get_transform()
328
341
    def test_replace_dangling_ie(self):
329
342
        create_tree, root = self.get_transform()
330
343
        # prepare tree
331
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
344
        root = create_tree.root
332
345
        create_tree.new_file('name1', root, 'hello1', 'name1')
333
346
        create_tree.apply()
334
347
        delete_contents = TreeTransform(self.wt)
381
394
                                         'dorothy-id')
382
395
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
383
396
        oz = conflicts.trans_id_tree_file_id('oz-id')
384
 
        # set up missing, unversioned parent
 
397
        # set up DeletedParent parent conflict
385
398
        conflicts.delete_versioned(oz)
386
399
        emerald = conflicts.trans_id_tree_file_id('emerald-id')
 
400
        # set up MissingParent conflict
 
401
        munchkincity = conflicts.trans_id_file_id('munchkincity-id')
 
402
        conflicts.adjust_path('munchkincity', root, munchkincity)
 
403
        conflicts.new_directory('auntem', munchkincity, 'auntem-id')
387
404
        # set up parent loop
388
405
        conflicts.adjust_path('emeraldcity', emerald, emerald)
389
406
        return conflicts, emerald, oz, old_dorothy, new_dorothy
410
427
                                   'dorothy.moved', 'dorothy', None,
411
428
                                   'dorothy-id')
412
429
        self.assertEqual(cooked_conflicts[1], duplicate_id)
413
 
        missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
 
430
        missing_parent = MissingParent('Created directory', 'munchkincity',
 
431
                                       'munchkincity-id')
 
432
        deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
414
433
        self.assertEqual(cooked_conflicts[2], missing_parent)
415
 
        unversioned_parent = UnversionedParent('Versioned directory', 'oz',
 
434
        unversioned_parent = UnversionedParent('Versioned directory',
 
435
                                               'munchkincity',
 
436
                                               'munchkincity-id')
 
437
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
416
438
                                               'oz-id')
417
439
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
418
440
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
419
441
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
420
 
        self.assertEqual(cooked_conflicts[4], parent_loop)
421
 
        self.assertEqual(len(cooked_conflicts), 5)
 
442
        self.assertEqual(cooked_conflicts[4], deleted_parent)
 
443
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
 
444
        self.assertEqual(cooked_conflicts[6], parent_loop)
 
445
        self.assertEqual(len(cooked_conflicts), 7)
422
446
        tt.finalize()
423
447
 
424
448
    def test_string_conflicts(self):
434
458
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
435
459
                                         'Unversioned existing file '
436
460
                                         'dorothy.moved.')
437
 
        self.assertEqual(conflicts_s[2], 'Conflict adding files to oz.  '
438
 
                                         'Not deleting.')
439
 
        self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
440
 
                                         'oz.  Versioned directory.')
441
 
        self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
 
461
        self.assertEqual(conflicts_s[2], 'Conflict adding files to'
 
462
                                         ' munchkincity.  Created directory.')
 
463
        self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
 
464
                                         ' versioned, but has versioned'
 
465
                                         ' children.  Versioned directory.')
 
466
        self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
 
467
                                         " is not empty.  Not deleting.")
 
468
        self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
 
469
                                         ' versioned, but has versioned'
 
470
                                         ' children.  Versioned directory.')
 
471
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
442
472
                                         ' oz/emeraldcity.  Cancelled move.')
443
473
 
444
474
    def test_moving_versioned_directories(self):
487
517
        create.apply()
488
518
        self.assertEqual(find_interesting(wt, wt, ['vfile']),
489
519
                         set(['myfile-id']))
490
 
        self.assertRaises(NotVersionedError, find_interesting, wt, wt,
 
520
        self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
491
521
                          ['uvfile'])
492
522
 
 
523
    def test_set_executability_order(self):
 
524
        """Ensure that executability behaves the same, no matter what order.
 
525
        
 
526
        - create file and set executability simultaneously
 
527
        - create file and set executability afterward
 
528
        - unsetting the executability of a file whose executability has not been
 
529
        declared should throw an exception (this may happen when a
 
530
        merge attempts to create a file with a duplicate ID)
 
531
        """
 
532
        transform, root = self.get_transform()
 
533
        wt = transform._tree
 
534
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
 
535
                           True)
 
536
        sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
 
537
        transform.set_executability(True, sac)
 
538
        uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
 
539
        self.assertRaises(KeyError, transform.set_executability, None, uws)
 
540
        transform.apply()
 
541
        self.assertTrue(wt.is_executable('soc'))
 
542
        self.assertTrue(wt.is_executable('sac'))
 
543
 
 
544
    def test_preserve_mode(self):
 
545
        """File mode is preserved when replacing content"""
 
546
        if sys.platform == 'win32':
 
547
            raise TestSkipped('chmod has no effect on win32')
 
548
        transform, root = self.get_transform()
 
549
        transform.new_file('file1', root, 'contents', 'file1-id', True)
 
550
        transform.apply()
 
551
        self.assertTrue(self.wt.is_executable('file1-id'))
 
552
        transform, root = self.get_transform()
 
553
        file1_id = transform.trans_id_tree_file_id('file1-id')
 
554
        transform.delete_contents(file1_id)
 
555
        transform.create_file('contents2', file1_id)
 
556
        transform.apply()
 
557
        self.assertTrue(self.wt.is_executable('file1-id'))
 
558
 
 
559
    def test__set_mode_stats_correctly(self):
 
560
        """_set_mode stats to determine file mode."""
 
561
        if sys.platform == 'win32':
 
562
            raise TestSkipped('chmod has no effect on win32')
 
563
 
 
564
        stat_paths = []
 
565
        real_stat = os.stat
 
566
        def instrumented_stat(path):
 
567
            stat_paths.append(path)
 
568
            return real_stat(path)
 
569
 
 
570
        transform, root = self.get_transform()
 
571
 
 
572
        bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
 
573
                                     file_id='bar-id-1', executable=False)
 
574
        transform.apply()
 
575
 
 
576
        transform, root = self.get_transform()
 
577
        bar1_id = transform.trans_id_tree_path('bar')
 
578
        bar2_id = transform.trans_id_tree_path('bar2')
 
579
        try:
 
580
            os.stat = instrumented_stat
 
581
            transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
 
582
        finally:
 
583
            os.stat = real_stat
 
584
            transform.finalize()
 
585
 
 
586
        bar1_abspath = self.wt.abspath('bar')
 
587
        self.assertEqual([bar1_abspath], stat_paths)
 
588
 
493
589
 
494
590
class TransformGroup(object):
495
 
    def __init__(self, dirname):
 
591
    def __init__(self, dirname, root_id):
496
592
        self.name = dirname
497
593
        os.mkdir(dirname)
498
594
        self.wt = BzrDir.create_standalone_workingtree(dirname)
 
595
        self.wt.set_root_id(root_id)
499
596
        self.b = self.wt.branch
500
597
        self.tt = TreeTransform(self.wt)
501
598
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
507
604
 
508
605
class TestTransformMerge(TestCaseInTempDir):
509
606
    def test_text_merge(self):
510
 
        base = TransformGroup("base")
 
607
        root_id = gen_root_id()
 
608
        base = TransformGroup("base", root_id)
511
609
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
512
610
        base.tt.new_file('b', base.root, 'b1', 'b')
513
611
        base.tt.new_file('c', base.root, 'c', 'c')
517
615
        base.tt.new_directory('g', base.root, 'g')
518
616
        base.tt.new_directory('h', base.root, 'h')
519
617
        base.tt.apply()
520
 
        other = TransformGroup("other")
 
618
        other = TransformGroup("other", root_id)
521
619
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
522
620
        other.tt.new_file('b', other.root, 'b2', 'b')
523
621
        other.tt.new_file('c', other.root, 'c2', 'c')
528
626
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
529
627
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
530
628
        other.tt.apply()
531
 
        this = TransformGroup("this")
 
629
        this = TransformGroup("this", root_id)
532
630
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
533
631
        this.tt.new_file('b', this.root, 'b', 'b')
534
632
        this.tt.new_file('c', this.root, 'c', 'c')
585
683
    def test_file_merge(self):
586
684
        if not has_symlinks():
587
685
            raise TestSkipped('Symlinks are not supported on this platform')
588
 
        base = TransformGroup("BASE")
589
 
        this = TransformGroup("THIS")
590
 
        other = TransformGroup("OTHER")
 
686
        root_id = gen_root_id()
 
687
        base = TransformGroup("BASE", root_id)
 
688
        this = TransformGroup("THIS", root_id)
 
689
        other = TransformGroup("OTHER", root_id)
591
690
        for tg in this, base, other:
592
691
            tg.tt.new_directory('a', tg.root, 'a')
593
692
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
625
724
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
626
725
 
627
726
    def test_filename_merge(self):
628
 
        base = TransformGroup("BASE")
629
 
        this = TransformGroup("THIS")
630
 
        other = TransformGroup("OTHER")
 
727
        root_id = gen_root_id()
 
728
        base = TransformGroup("BASE", root_id)
 
729
        this = TransformGroup("THIS", root_id)
 
730
        other = TransformGroup("OTHER", root_id)
631
731
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
632
732
                                   for t in [base, this, other]]
633
733
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
657
757
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
658
758
 
659
759
    def test_filename_merge_conflicts(self):
660
 
        base = TransformGroup("BASE")
661
 
        this = TransformGroup("THIS")
662
 
        other = TransformGroup("OTHER")
 
760
        root_id = gen_root_id()
 
761
        base = TransformGroup("BASE", root_id)
 
762
        this = TransformGroup("THIS", root_id)
 
763
        other = TransformGroup("OTHER", root_id)
663
764
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
664
765
                                   for t in [base, this, other]]
665
766
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
686
787
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
687
788
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
688
789
 
689
 
class TestBuildTree(TestCaseInTempDir):
 
790
 
 
791
class TestBuildTree(tests.TestCaseWithTransport):
 
792
 
690
793
    def test_build_tree(self):
691
794
        if not has_symlinks():
692
795
            raise TestSkipped('Test requires symlink support')
702
805
        self.assertIs(os.path.isdir('b/foo'), True)
703
806
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
704
807
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
 
808
 
 
809
    def test_file_conflict_handling(self):
 
810
        """Ensure that when building trees, conflict handling is done"""
 
811
        source = self.make_branch_and_tree('source')
 
812
        target = self.make_branch_and_tree('target')
 
813
        self.build_tree(['source/file', 'target/file'])
 
814
        source.add('file', 'new-file')
 
815
        source.commit('added file')
 
816
        build_tree(source.basis_tree(), target)
 
817
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
818
                          'file.moved', 'file', None, 'new-file')],
 
819
                         target.conflicts())
 
820
        target2 = self.make_branch_and_tree('target2')
 
821
        target_file = file('target2/file', 'wb')
 
822
        try:
 
823
            source_file = file('source/file', 'rb')
 
824
            try:
 
825
                target_file.write(source_file.read())
 
826
            finally:
 
827
                source_file.close()
 
828
        finally:
 
829
            target_file.close()
 
830
        build_tree(source.basis_tree(), target2)
 
831
        self.assertEqual([], target2.conflicts())
 
832
 
 
833
    def test_symlink_conflict_handling(self):
 
834
        """Ensure that when building trees, conflict handling is done"""
 
835
        if not has_symlinks():
 
836
            raise TestSkipped('Test requires symlink support')
 
837
        source = self.make_branch_and_tree('source')
 
838
        os.symlink('foo', 'source/symlink')
 
839
        source.add('symlink', 'new-symlink')
 
840
        source.commit('added file')
 
841
        target = self.make_branch_and_tree('target')
 
842
        os.symlink('bar', 'target/symlink')
 
843
        build_tree(source.basis_tree(), target)
 
844
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
845
            'symlink.moved', 'symlink', None, 'new-symlink')],
 
846
            target.conflicts())
 
847
        target = self.make_branch_and_tree('target2')
 
848
        os.symlink('foo', 'target2/symlink')
 
849
        build_tree(source.basis_tree(), target)
 
850
        self.assertEqual([], target.conflicts())
705
851
        
 
852
    def test_directory_conflict_handling(self):
 
853
        """Ensure that when building trees, conflict handling is done"""
 
854
        source = self.make_branch_and_tree('source')
 
855
        target = self.make_branch_and_tree('target')
 
856
        self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
 
857
        source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
 
858
        source.commit('added file')
 
859
        build_tree(source.basis_tree(), target)
 
860
        self.assertEqual([], target.conflicts())
 
861
        self.failUnlessExists('target/dir1/file')
 
862
 
 
863
        # Ensure contents are merged
 
864
        target = self.make_branch_and_tree('target2')
 
865
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
 
866
        build_tree(source.basis_tree(), target)
 
867
        self.assertEqual([], target.conflicts())
 
868
        self.failUnlessExists('target2/dir1/file2')
 
869
        self.failUnlessExists('target2/dir1/file')
 
870
 
 
871
        # Ensure new contents are suppressed for existing branches
 
872
        target = self.make_branch_and_tree('target3')
 
873
        self.make_branch('target3/dir1')
 
874
        self.build_tree(['target3/dir1/file2'])
 
875
        build_tree(source.basis_tree(), target)
 
876
        self.failIfExists('target3/dir1/file')
 
877
        self.failUnlessExists('target3/dir1/file2')
 
878
        self.failUnlessExists('target3/dir1.diverted/file')
 
879
        self.assertEqual([DuplicateEntry('Diverted to',
 
880
            'dir1.diverted', 'dir1', 'new-dir1', None)],
 
881
            target.conflicts())
 
882
 
 
883
        target = self.make_branch_and_tree('target4')
 
884
        self.build_tree(['target4/dir1/'])
 
885
        self.make_branch('target4/dir1/file')
 
886
        build_tree(source.basis_tree(), target)
 
887
        self.failUnlessExists('target4/dir1/file')
 
888
        self.assertEqual('directory', file_kind('target4/dir1/file'))
 
889
        self.failUnlessExists('target4/dir1/file.diverted')
 
890
        self.assertEqual([DuplicateEntry('Diverted to',
 
891
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
 
892
            target.conflicts())
 
893
 
 
894
    def test_mixed_conflict_handling(self):
 
895
        """Ensure that when building trees, conflict handling is done"""
 
896
        source = self.make_branch_and_tree('source')
 
897
        target = self.make_branch_and_tree('target')
 
898
        self.build_tree(['source/name', 'target/name/'])
 
899
        source.add('name', 'new-name')
 
900
        source.commit('added file')
 
901
        build_tree(source.basis_tree(), target)
 
902
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
903
            'name.moved', 'name', None, 'new-name')], target.conflicts())
 
904
 
 
905
    def test_raises_in_populated(self):
 
906
        source = self.make_branch_and_tree('source')
 
907
        self.build_tree(['source/name'])
 
908
        source.add('name')
 
909
        source.commit('added name')
 
910
        target = self.make_branch_and_tree('target')
 
911
        self.build_tree(['target/name'])
 
912
        target.add('name')
 
913
        self.assertRaises(AssertionError, build_tree, source.basis_tree(),
 
914
                          target)
 
915
 
 
916
 
706
917
class MockTransform(object):
707
918
 
708
919
    def has_named_child(self, by_parent, parent_id, name):