~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Wouter van Heyst
  • Date: 2006-06-07 16:05:27 UTC
  • mto: This revision was merged to the branch mainline in revision 1752.
  • Revision ID: larstiq@larstiq.dyndns.org-20060607160527-2b3649154d0e2e84
more code cleanup

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
20
18
 
21
 
from bzrlib import (
22
 
    errors,
23
 
    generate_ids,
24
 
    symbol_versioning,
25
 
    tests,
26
 
    urlutils,
27
 
    )
28
19
from bzrlib.bzrdir import BzrDir
29
20
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
30
 
                              UnversionedParent, ParentLoop, DeletingParent,)
 
21
                              UnversionedParent, ParentLoop)
31
22
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
32
 
                           ReusingTransform, CantMoveRoot, 
33
 
                           PathsNotVersionedError, ExistingLimbo,
34
 
                           ImmortalLimbo, LockError)
 
23
                           ReusingTransform, CantMoveRoot, NotVersionedError,
 
24
                           ExistingLimbo, ImmortalLimbo, LockError)
35
25
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
36
26
from bzrlib.merge import Merge3Merger
37
27
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
38
28
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
39
29
                              resolve_conflicts, cook_conflicts, 
40
30
                              find_interesting, build_tree, get_backup_name)
41
 
 
42
 
 
43
 
class TestTreeTransform(tests.TestCaseWithTransport):
 
31
import bzrlib.urlutils as urlutils
 
32
 
 
33
class TestTreeTransform(TestCaseInTempDir):
44
34
 
45
35
    def setUp(self):
46
36
        super(TestTreeTransform, self).setUp()
47
 
        self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
 
37
        self.wt = BzrDir.create_standalone_workingtree('.')
48
38
        os.chdir('..')
49
39
 
50
40
    def get_transform(self):
51
41
        transform = TreeTransform(self.wt)
52
42
        #self.addCleanup(transform.finalize)
53
 
        return transform, transform.root
 
43
        return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
54
44
 
55
45
    def test_existing_limbo(self):
56
46
        limbo_name = urlutils.local_path_from_url(
127
117
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
128
118
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
129
119
 
130
 
        self.assertEqual('toto-contents',
 
120
        self.assertEqual('toto-contents', 
131
121
                         self.wt.get_file_byname('oz/dorothy/toto').read())
132
122
        self.assertIs(self.wt.is_executable('toto-id'), False)
133
123
 
134
 
    def test_tree_reference(self):
135
 
        transform, root = self.get_transform()
136
 
        tree = transform._tree
137
 
        trans_id = transform.new_directory('reference', root, 'subtree-id')
138
 
        transform.set_tree_reference('subtree-revision', trans_id)
139
 
        transform.apply()
140
 
        tree.lock_read()
141
 
        self.addCleanup(tree.unlock)
142
 
        self.assertEqual('subtree-revision',
143
 
                         tree.inventory['subtree-id'].reference_revision)
144
 
 
145
124
    def test_conflicts(self):
146
125
        transform, root = self.get_transform()
147
126
        trans_id = transform.new_file('name', root, 'contents', 
211
190
        transform3.delete_contents(oz_id)
212
191
        self.assertEqual(transform3.find_conflicts(), 
213
192
                         [('missing parent', oz_id)])
214
 
        root_id = transform3.root
 
193
        root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
215
194
        tip_id = transform3.trans_id_tree_file_id('tip-id')
216
195
        transform3.adjust_path('tip', root_id, tip_id)
217
196
        transform3.apply()
243
222
    def test_name_invariants(self):
244
223
        create_tree, root = self.get_transform()
245
224
        # prepare tree
246
 
        root = create_tree.root
 
225
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
247
226
        create_tree.new_file('name1', root, 'hello1', 'name1')
248
227
        create_tree.new_file('name2', root, 'hello2', 'name2')
249
228
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
253
232
        create_tree.apply()
254
233
 
255
234
        mangle_tree,root = self.get_transform()
256
 
        root = mangle_tree.root
 
235
        root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
257
236
        #swap names
258
237
        name1 = mangle_tree.trans_id_tree_file_id('name1')
259
238
        name2 = mangle_tree.trans_id_tree_file_id('name2')
338
317
    def test_move_dangling_ie(self):
339
318
        create_tree, root = self.get_transform()
340
319
        # prepare tree
341
 
        root = create_tree.root
 
320
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
342
321
        create_tree.new_file('name1', root, 'hello1', 'name1')
343
322
        create_tree.apply()
344
323
        delete_contents, root = self.get_transform()
354
333
    def test_replace_dangling_ie(self):
355
334
        create_tree, root = self.get_transform()
356
335
        # prepare tree
357
 
        root = create_tree.root
 
336
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
358
337
        create_tree.new_file('name1', root, 'hello1', 'name1')
359
338
        create_tree.apply()
360
339
        delete_contents = TreeTransform(self.wt)
394
373
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
395
374
                         'wizard-target')
396
375
 
 
376
 
397
377
    def get_conflicted(self):
398
378
        create,root = self.get_transform()
399
379
        create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
406
386
                                         'dorothy-id')
407
387
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
408
388
        oz = conflicts.trans_id_tree_file_id('oz-id')
409
 
        # set up DeletedParent parent conflict
 
389
        # set up missing, unversioned parent
410
390
        conflicts.delete_versioned(oz)
411
391
        emerald = conflicts.trans_id_tree_file_id('emerald-id')
412
 
        # set up MissingParent conflict
413
 
        munchkincity = conflicts.trans_id_file_id('munchkincity-id')
414
 
        conflicts.adjust_path('munchkincity', root, munchkincity)
415
 
        conflicts.new_directory('auntem', munchkincity, 'auntem-id')
416
392
        # set up parent loop
417
393
        conflicts.adjust_path('emeraldcity', emerald, emerald)
418
394
        return conflicts, emerald, oz, old_dorothy, new_dorothy
439
415
                                   'dorothy.moved', 'dorothy', None,
440
416
                                   'dorothy-id')
441
417
        self.assertEqual(cooked_conflicts[1], duplicate_id)
442
 
        missing_parent = MissingParent('Created directory', 'munchkincity',
443
 
                                       'munchkincity-id')
444
 
        deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
 
418
        missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
445
419
        self.assertEqual(cooked_conflicts[2], missing_parent)
446
 
        unversioned_parent = UnversionedParent('Versioned directory',
447
 
                                               'munchkincity',
448
 
                                               'munchkincity-id')
449
 
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
 
420
        unversioned_parent = UnversionedParent('Versioned directory', 'oz',
450
421
                                               'oz-id')
451
422
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
452
423
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
453
424
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
454
 
        self.assertEqual(cooked_conflicts[4], deleted_parent)
455
 
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
456
 
        self.assertEqual(cooked_conflicts[6], parent_loop)
457
 
        self.assertEqual(len(cooked_conflicts), 7)
 
425
        self.assertEqual(cooked_conflicts[4], parent_loop)
 
426
        self.assertEqual(len(cooked_conflicts), 5)
458
427
        tt.finalize()
459
428
 
460
429
    def test_string_conflicts(self):
470
439
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
471
440
                                         'Unversioned existing file '
472
441
                                         'dorothy.moved.')
473
 
        self.assertEqual(conflicts_s[2], 'Conflict adding files to'
474
 
                                         ' munchkincity.  Created directory.')
475
 
        self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
476
 
                                         ' versioned, but has versioned'
477
 
                                         ' children.  Versioned directory.')
478
 
        self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
479
 
                                         " is not empty.  Not deleting.")
480
 
        self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
481
 
                                         ' versioned, but has versioned'
482
 
                                         ' children.  Versioned directory.')
483
 
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
 
442
        self.assertEqual(conflicts_s[2], 'Conflict adding files to oz.  '
 
443
                                         'Not deleting.')
 
444
        self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
 
445
                                         'oz.  Versioned directory.')
 
446
        self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
484
447
                                         ' oz/emeraldcity.  Cancelled move.')
485
448
 
486
449
    def test_moving_versioned_directories(self):
527
490
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
528
491
        create.new_file('uvfile', root, 'othertext')
529
492
        create.apply()
530
 
        result = self.applyDeprecated(symbol_versioning.zero_fifteen,
531
 
            find_interesting, wt, wt, ['vfile'])
532
 
        self.assertEqual(result, set(['myfile-id']))
 
493
        self.assertEqual(find_interesting(wt, wt, ['vfile']),
 
494
                         set(['myfile-id']))
 
495
        self.assertRaises(NotVersionedError, find_interesting, wt, wt,
 
496
                          ['uvfile'])
533
497
 
534
498
    def test_set_executability_order(self):
535
499
        """Ensure that executability behaves the same, no matter what order.
544
508
        wt = transform._tree
545
509
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
546
510
                           True)
547
 
        sac = transform.new_file('set_after_creation', root,
548
 
                                 'Set after creation', 'sac')
 
511
        sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
549
512
        transform.set_executability(True, sac)
550
 
        uws = transform.new_file('unset_without_set', root, 'Unset badly',
551
 
                                 'uws')
 
513
        uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
552
514
        self.assertRaises(KeyError, transform.set_executability, None, uws)
553
515
        transform.apply()
554
516
        self.assertTrue(wt.is_executable('soc'))
555
517
        self.assertTrue(wt.is_executable('sac'))
556
518
 
557
 
    def test_preserve_mode(self):
558
 
        """File mode is preserved when replacing content"""
559
 
        if sys.platform == 'win32':
560
 
            raise TestSkipped('chmod has no effect on win32')
561
 
        transform, root = self.get_transform()
562
 
        transform.new_file('file1', root, 'contents', 'file1-id', True)
563
 
        transform.apply()
564
 
        self.assertTrue(self.wt.is_executable('file1-id'))
565
 
        transform, root = self.get_transform()
566
 
        file1_id = transform.trans_id_tree_file_id('file1-id')
567
 
        transform.delete_contents(file1_id)
568
 
        transform.create_file('contents2', file1_id)
569
 
        transform.apply()
570
 
        self.assertTrue(self.wt.is_executable('file1-id'))
571
 
 
572
 
    def test__set_mode_stats_correctly(self):
573
 
        """_set_mode stats to determine file mode."""
574
 
        if sys.platform == 'win32':
575
 
            raise TestSkipped('chmod has no effect on win32')
576
 
 
577
 
        stat_paths = []
578
 
        real_stat = os.stat
579
 
        def instrumented_stat(path):
580
 
            stat_paths.append(path)
581
 
            return real_stat(path)
582
 
 
583
 
        transform, root = self.get_transform()
584
 
 
585
 
        bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
586
 
                                     file_id='bar-id-1', executable=False)
587
 
        transform.apply()
588
 
 
589
 
        transform, root = self.get_transform()
590
 
        bar1_id = transform.trans_id_tree_path('bar')
591
 
        bar2_id = transform.trans_id_tree_path('bar2')
592
 
        try:
593
 
            os.stat = instrumented_stat
594
 
            transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
595
 
        finally:
596
 
            os.stat = real_stat
597
 
            transform.finalize()
598
 
 
599
 
        bar1_abspath = self.wt.abspath('bar')
600
 
        self.assertEqual([bar1_abspath], stat_paths)
601
 
 
602
 
    def test_iter_changes(self):
603
 
        self.wt.set_root_id('eert_toor')
604
 
        transform, root = self.get_transform()
605
 
        transform.new_file('old', root, 'blah', 'id-1', True)
606
 
        transform.apply()
607
 
        transform, root = self.get_transform()
608
 
        try:
609
 
            self.assertEqual([], list(transform._iter_changes()))
610
 
            old = transform.trans_id_tree_file_id('id-1')
611
 
            transform.unversion_file(old)
612
 
            self.assertEqual([('id-1', ('old', None), False, (True, False),
613
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
614
 
                (True, True))], list(transform._iter_changes()))
615
 
            transform.new_directory('new', root, 'id-1')
616
 
            self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
617
 
                ('eert_toor', 'eert_toor'), ('old', 'new'),
618
 
                ('file', 'directory'),
619
 
                (True, False))], list(transform._iter_changes()))
620
 
        finally:
621
 
            transform.finalize()
622
 
 
623
 
    def test_iter_changes_new(self):
624
 
        self.wt.set_root_id('eert_toor')
625
 
        transform, root = self.get_transform()
626
 
        transform.new_file('old', root, 'blah')
627
 
        transform.apply()
628
 
        transform, root = self.get_transform()
629
 
        try:
630
 
            old = transform.trans_id_tree_path('old')
631
 
            transform.version_file('id-1', old)
632
 
            self.assertEqual([('id-1', (None, 'old'), False, (False, True),
633
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
634
 
                (False, False))], list(transform._iter_changes()))
635
 
        finally:
636
 
            transform.finalize()
637
 
 
638
 
    def test_iter_changes_modifications(self):
639
 
        self.wt.set_root_id('eert_toor')
640
 
        transform, root = self.get_transform()
641
 
        transform.new_file('old', root, 'blah', 'id-1')
642
 
        transform.new_file('new', root, 'blah')
643
 
        transform.new_directory('subdir', root, 'subdir-id')
644
 
        transform.apply()
645
 
        transform, root = self.get_transform()
646
 
        try:
647
 
            old = transform.trans_id_tree_path('old')
648
 
            subdir = transform.trans_id_tree_file_id('subdir-id')
649
 
            new = transform.trans_id_tree_path('new')
650
 
            self.assertEqual([], list(transform._iter_changes()))
651
 
 
652
 
            #content deletion
653
 
            transform.delete_contents(old)
654
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
655
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
656
 
                (False, False))], list(transform._iter_changes()))
657
 
 
658
 
            #content change
659
 
            transform.create_file('blah', old)
660
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
661
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
662
 
                (False, False))], list(transform._iter_changes()))
663
 
            transform.cancel_deletion(old)
664
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
665
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
666
 
                (False, False))], list(transform._iter_changes()))
667
 
            transform.cancel_creation(old)
668
 
 
669
 
            # move file_id to a different file
670
 
            self.assertEqual([], list(transform._iter_changes()))
671
 
            transform.unversion_file(old)
672
 
            transform.version_file('id-1', new)
673
 
            transform.adjust_path('old', root, new)
674
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
675
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
676
 
                (False, False))], list(transform._iter_changes()))
677
 
            transform.cancel_versioning(new)
678
 
            transform._removed_id = set()
679
 
 
680
 
            #execute bit
681
 
            self.assertEqual([], list(transform._iter_changes()))
682
 
            transform.set_executability(True, old)
683
 
            self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
684
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
685
 
                (False, True))], list(transform._iter_changes()))
686
 
            transform.set_executability(None, old)
687
 
 
688
 
            # filename
689
 
            self.assertEqual([], list(transform._iter_changes()))
690
 
            transform.adjust_path('new', root, old)
691
 
            transform._new_parent = {}
692
 
            self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
693
 
                ('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
694
 
                (False, False))], list(transform._iter_changes()))
695
 
            transform._new_name = {}
696
 
 
697
 
            # parent directory
698
 
            self.assertEqual([], list(transform._iter_changes()))
699
 
            transform.adjust_path('new', subdir, old)
700
 
            transform._new_name = {}
701
 
            self.assertEqual([('id-1', ('old', 'subdir/old'), False,
702
 
                (True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
703
 
                ('file', 'file'), (False, False))],
704
 
                list(transform._iter_changes()))
705
 
            transform._new_path = {}
706
 
 
707
 
        finally:
708
 
            transform.finalize()
709
 
 
710
 
    def test_iter_changes_modified_bleed(self):
711
 
        self.wt.set_root_id('eert_toor')
712
 
        """Modified flag should not bleed from one change to another"""
713
 
        # unfortunately, we have no guarantee that file1 (which is modified)
714
 
        # will be applied before file2.  And if it's applied after file2, it
715
 
        # obviously can't bleed into file2's change output.  But for now, it
716
 
        # works.
717
 
        transform, root = self.get_transform()
718
 
        transform.new_file('file1', root, 'blah', 'id-1')
719
 
        transform.new_file('file2', root, 'blah', 'id-2')
720
 
        transform.apply()
721
 
        transform, root = self.get_transform()
722
 
        try:
723
 
            transform.delete_contents(transform.trans_id_file_id('id-1'))
724
 
            transform.set_executability(True,
725
 
            transform.trans_id_file_id('id-2'))
726
 
            self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
727
 
                ('eert_toor', 'eert_toor'), ('file1', u'file1'),
728
 
                ('file', None), (False, False)),
729
 
                ('id-2', (u'file2', u'file2'), False, (True, True),
730
 
                ('eert_toor', 'eert_toor'), ('file2', u'file2'),
731
 
                ('file', 'file'), (False, True))],
732
 
                list(transform._iter_changes()))
733
 
        finally:
734
 
            transform.finalize()
735
 
 
736
 
    def test_iter_changes_move_missing(self):
737
 
        """Test moving ids with no files around"""
738
 
        self.wt.set_root_id('toor_eert')
739
 
        # Need two steps because versioning a non-existant file is a conflict.
740
 
        transform, root = self.get_transform()
741
 
        transform.new_directory('floater', root, 'floater-id')
742
 
        transform.apply()
743
 
        transform, root = self.get_transform()
744
 
        transform.delete_contents(transform.trans_id_tree_path('floater'))
745
 
        transform.apply()
746
 
        transform, root = self.get_transform()
747
 
        floater = transform.trans_id_tree_path('floater')
748
 
        try:
749
 
            transform.adjust_path('flitter', root, floater)
750
 
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
751
 
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
752
 
            (None, None), (False, False))], list(transform._iter_changes()))
753
 
        finally:
754
 
            transform.finalize()
755
 
 
756
 
    def test_iter_changes_pointless(self):
757
 
        """Ensure that no-ops are not treated as modifications"""
758
 
        self.wt.set_root_id('eert_toor')
759
 
        transform, root = self.get_transform()
760
 
        transform.new_file('old', root, 'blah', 'id-1')
761
 
        transform.new_directory('subdir', root, 'subdir-id')
762
 
        transform.apply()
763
 
        transform, root = self.get_transform()
764
 
        try:
765
 
            old = transform.trans_id_tree_path('old')
766
 
            subdir = transform.trans_id_tree_file_id('subdir-id')
767
 
            self.assertEqual([], list(transform._iter_changes()))
768
 
            transform.delete_contents(subdir)
769
 
            transform.create_directory(subdir)
770
 
            transform.set_executability(False, old)
771
 
            transform.unversion_file(old)
772
 
            transform.version_file('id-1', old)
773
 
            transform.adjust_path('old', root, old)
774
 
            self.assertEqual([], list(transform._iter_changes()))
775
 
        finally:
776
 
            transform.finalize()
777
 
 
778
 
    def test_rename_count(self):
779
 
        transform, root = self.get_transform()
780
 
        transform.new_file('name1', root, 'contents')
781
 
        self.assertEqual(transform.rename_count, 0)
782
 
        transform.apply()
783
 
        self.assertEqual(transform.rename_count, 1)
784
 
        transform2, root = self.get_transform()
785
 
        transform2.adjust_path('name2', root,
786
 
                               transform2.trans_id_tree_path('name1'))
787
 
        self.assertEqual(transform2.rename_count, 0)
788
 
        transform2.apply()
789
 
        self.assertEqual(transform2.rename_count, 2)
790
 
 
791
 
    def test_change_parent(self):
792
 
        """Ensure that after we change a parent, the results are still right.
793
 
 
794
 
        Renames and parent changes on pending transforms can happen as part
795
 
        of conflict resolution, and are explicitly permitted by the
796
 
        TreeTransform API.
797
 
 
798
 
        This test ensures they work correctly with the rename-avoidance
799
 
        optimization.
800
 
        """
801
 
        transform, root = self.get_transform()
802
 
        parent1 = transform.new_directory('parent1', root)
803
 
        child1 = transform.new_file('child1', parent1, 'contents')
804
 
        parent2 = transform.new_directory('parent2', root)
805
 
        transform.adjust_path('child1', parent2, child1)
806
 
        transform.apply()
807
 
        self.failIfExists(self.wt.abspath('parent1/child1'))
808
 
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
809
 
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
810
 
        # no rename for child1 (counting only renames during apply)
811
 
        self.failUnlessEqual(2, transform.rename_count)
812
 
 
813
 
    def test_cancel_parent(self):
814
 
        """Cancelling a parent doesn't cause deletion of a non-empty directory
815
 
 
816
 
        This is like the test_change_parent, except that we cancel the parent
817
 
        before adjusting the path.  The transform must detect that the
818
 
        directory is non-empty, and move children to safe locations.
819
 
        """
820
 
        transform, root = self.get_transform()
821
 
        parent1 = transform.new_directory('parent1', root)
822
 
        child1 = transform.new_file('child1', parent1, 'contents')
823
 
        child2 = transform.new_file('child2', parent1, 'contents')
824
 
        try:
825
 
            transform.cancel_creation(parent1)
826
 
        except OSError:
827
 
            self.fail('Failed to move child1 before deleting parent1')
828
 
        transform.cancel_creation(child2)
829
 
        transform.create_directory(parent1)
830
 
        try:
831
 
            transform.cancel_creation(parent1)
832
 
        # If the transform incorrectly believes that child2 is still in
833
 
        # parent1's limbo directory, it will try to rename it and fail
834
 
        # because was already moved by the first cancel_creation.
835
 
        except OSError:
836
 
            self.fail('Transform still thinks child2 is a child of parent1')
837
 
        parent2 = transform.new_directory('parent2', root)
838
 
        transform.adjust_path('child1', parent2, child1)
839
 
        transform.apply()
840
 
        self.failIfExists(self.wt.abspath('parent1'))
841
 
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
842
 
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
843
 
        self.failUnlessEqual(2, transform.rename_count)
844
 
 
845
 
    def test_adjust_and_cancel(self):
846
 
        """Make sure adjust_path keeps track of limbo children properly"""
847
 
        transform, root = self.get_transform()
848
 
        parent1 = transform.new_directory('parent1', root)
849
 
        child1 = transform.new_file('child1', parent1, 'contents')
850
 
        parent2 = transform.new_directory('parent2', root)
851
 
        transform.adjust_path('child1', parent2, child1)
852
 
        transform.cancel_creation(child1)
853
 
        try:
854
 
            transform.cancel_creation(parent1)
855
 
        # if the transform thinks child1 is still in parent1's limbo
856
 
        # directory, it will attempt to move it and fail.
857
 
        except OSError:
858
 
            self.fail('Transform still thinks child1 is a child of parent1')
859
 
        transform.finalize()
860
 
 
861
 
    def test_noname_contents(self):
862
 
        """TreeTransform should permit deferring naming files."""
863
 
        transform, root = self.get_transform()
864
 
        parent = transform.trans_id_file_id('parent-id')
865
 
        try:
866
 
            transform.create_directory(parent)
867
 
        except KeyError:
868
 
            self.fail("Can't handle contents with no name")
869
 
        transform.finalize()
870
 
 
871
 
    def test_noname_contents_nested(self):
872
 
        """TreeTransform should permit deferring naming files."""
873
 
        transform, root = self.get_transform()
874
 
        parent = transform.trans_id_file_id('parent-id')
875
 
        try:
876
 
            transform.create_directory(parent)
877
 
        except KeyError:
878
 
            self.fail("Can't handle contents with no name")
879
 
        child = transform.new_directory('child', parent)
880
 
        transform.adjust_path('parent', root, parent)
881
 
        transform.apply()
882
 
        self.failUnlessExists(self.wt.abspath('parent/child'))
883
 
        self.assertEqual(1, transform.rename_count)
884
 
 
885
 
    def test_reuse_name(self):
886
 
        """Avoid reusing the same limbo name for different files"""
887
 
        transform, root = self.get_transform()
888
 
        parent = transform.new_directory('parent', root)
889
 
        child1 = transform.new_directory('child', parent)
890
 
        try:
891
 
            child2 = transform.new_directory('child', parent)
892
 
        except OSError:
893
 
            self.fail('Tranform tried to use the same limbo name twice')
894
 
        transform.adjust_path('child2', parent, child2)
895
 
        transform.apply()
896
 
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
897
 
        # child2 is put into top-level limbo because child1 has already
898
 
        # claimed the direct limbo path when child2 is created.  There is no
899
 
        # advantage in renaming files once they're in top-level limbo, except
900
 
        # as part of apply.
901
 
        self.assertEqual(2, transform.rename_count)
902
 
 
903
 
    def test_reuse_when_first_moved(self):
904
 
        """Don't avoid direct paths when it is safe to use them"""
905
 
        transform, root = self.get_transform()
906
 
        parent = transform.new_directory('parent', root)
907
 
        child1 = transform.new_directory('child', parent)
908
 
        transform.adjust_path('child1', parent, child1)
909
 
        child2 = transform.new_directory('child', parent)
910
 
        transform.apply()
911
 
        # limbo/new-1 => parent
912
 
        self.assertEqual(1, transform.rename_count)
913
 
 
914
 
    def test_reuse_after_cancel(self):
915
 
        """Don't avoid direct paths when it is safe to use them"""
916
 
        transform, root = self.get_transform()
917
 
        parent2 = transform.new_directory('parent2', root)
918
 
        child1 = transform.new_directory('child1', parent2)
919
 
        transform.cancel_creation(parent2)
920
 
        transform.create_directory(parent2)
921
 
        child2 = transform.new_directory('child1', parent2)
922
 
        transform.adjust_path('child2', parent2, child1)
923
 
        transform.apply()
924
 
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
925
 
        self.assertEqual(2, transform.rename_count)
926
 
 
927
 
    def test_finalize_order(self):
928
 
        """Finalize must be done in child-to-parent order"""
929
 
        transform, root = self.get_transform()
930
 
        parent = transform.new_directory('parent', root)
931
 
        child = transform.new_directory('child', parent)
932
 
        try:
933
 
            transform.finalize()
934
 
        except OSError:
935
 
            self.fail('Tried to remove parent before child1')
936
 
 
937
 
    def test_cancel_with_cancelled_child_should_succeed(self):
938
 
        transform, root = self.get_transform()
939
 
        parent = transform.new_directory('parent', root)
940
 
        child = transform.new_directory('child', parent)
941
 
        transform.cancel_creation(child)
942
 
        transform.cancel_creation(parent)
943
 
        transform.finalize()
944
 
 
945
519
 
946
520
class TransformGroup(object):
947
 
    def __init__(self, dirname, root_id):
 
521
    def __init__(self, dirname):
948
522
        self.name = dirname
949
523
        os.mkdir(dirname)
950
524
        self.wt = BzrDir.create_standalone_workingtree(dirname)
951
 
        self.wt.set_root_id(root_id)
952
525
        self.b = self.wt.branch
953
526
        self.tt = TreeTransform(self.wt)
954
527
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
955
528
 
956
 
 
957
529
def conflict_text(tree, merge):
958
530
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
959
531
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
961
533
 
962
534
class TestTransformMerge(TestCaseInTempDir):
963
535
    def test_text_merge(self):
964
 
        root_id = generate_ids.gen_root_id()
965
 
        base = TransformGroup("base", root_id)
 
536
        base = TransformGroup("base")
966
537
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
967
538
        base.tt.new_file('b', base.root, 'b1', 'b')
968
539
        base.tt.new_file('c', base.root, 'c', 'c')
972
543
        base.tt.new_directory('g', base.root, 'g')
973
544
        base.tt.new_directory('h', base.root, 'h')
974
545
        base.tt.apply()
975
 
        other = TransformGroup("other", root_id)
 
546
        other = TransformGroup("other")
976
547
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
977
548
        other.tt.new_file('b', other.root, 'b2', 'b')
978
549
        other.tt.new_file('c', other.root, 'c2', 'c')
983
554
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
984
555
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
985
556
        other.tt.apply()
986
 
        this = TransformGroup("this", root_id)
 
557
        this = TransformGroup("this")
987
558
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
988
559
        this.tt.new_file('b', this.root, 'b', 'b')
989
560
        this.tt.new_file('c', this.root, 'c', 'c')
1040
611
    def test_file_merge(self):
1041
612
        if not has_symlinks():
1042
613
            raise TestSkipped('Symlinks are not supported on this platform')
1043
 
        root_id = generate_ids.gen_root_id()
1044
 
        base = TransformGroup("BASE", root_id)
1045
 
        this = TransformGroup("THIS", root_id)
1046
 
        other = TransformGroup("OTHER", root_id)
 
614
        base = TransformGroup("BASE")
 
615
        this = TransformGroup("THIS")
 
616
        other = TransformGroup("OTHER")
1047
617
        for tg in this, base, other:
1048
618
            tg.tt.new_directory('a', tg.root, 'a')
1049
619
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
1081
651
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
1082
652
 
1083
653
    def test_filename_merge(self):
1084
 
        root_id = generate_ids.gen_root_id()
1085
 
        base = TransformGroup("BASE", root_id)
1086
 
        this = TransformGroup("THIS", root_id)
1087
 
        other = TransformGroup("OTHER", root_id)
 
654
        base = TransformGroup("BASE")
 
655
        this = TransformGroup("THIS")
 
656
        other = TransformGroup("OTHER")
1088
657
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
1089
658
                                   for t in [base, this, other]]
1090
659
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
1114
683
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
1115
684
 
1116
685
    def test_filename_merge_conflicts(self):
1117
 
        root_id = generate_ids.gen_root_id()
1118
 
        base = TransformGroup("BASE", root_id)
1119
 
        this = TransformGroup("THIS", root_id)
1120
 
        other = TransformGroup("OTHER", root_id)
 
686
        base = TransformGroup("BASE")
 
687
        this = TransformGroup("THIS")
 
688
        other = TransformGroup("OTHER")
1121
689
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
1122
690
                                   for t in [base, this, other]]
1123
691
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
1144
712
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
1145
713
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
1146
714
 
1147
 
 
1148
 
class TestBuildTree(tests.TestCaseWithTransport):
1149
 
 
 
715
class TestBuildTree(TestCaseInTempDir):
1150
716
    def test_build_tree(self):
1151
717
        if not has_symlinks():
1152
718
            raise TestSkipped('Test requires symlink support')
1158
724
        a.add(['foo', 'foo/bar', 'foo/baz'])
1159
725
        a.commit('initial commit')
1160
726
        b = BzrDir.create_standalone_workingtree('b')
1161
 
        basis = a.basis_tree()
1162
 
        basis.lock_read()
1163
 
        self.addCleanup(basis.unlock)
1164
 
        build_tree(basis, b)
 
727
        build_tree(a.basis_tree(), b)
1165
728
        self.assertIs(os.path.isdir('b/foo'), True)
1166
729
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1167
730
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1168
 
 
1169
 
    def test_build_with_references(self):
1170
 
        tree = self.make_branch_and_tree('source',
1171
 
            format='dirstate-with-subtree')
1172
 
        subtree = self.make_branch_and_tree('source/subtree',
1173
 
            format='dirstate-with-subtree')
1174
 
        tree.add_reference(subtree)
1175
 
        tree.commit('a revision')
1176
 
        tree.branch.create_checkout('target')
1177
 
        self.failUnlessExists('target')
1178
 
        self.failUnlessExists('target/subtree')
1179
 
 
1180
 
    def test_file_conflict_handling(self):
1181
 
        """Ensure that when building trees, conflict handling is done"""
1182
 
        source = self.make_branch_and_tree('source')
1183
 
        target = self.make_branch_and_tree('target')
1184
 
        self.build_tree(['source/file', 'target/file'])
1185
 
        source.add('file', 'new-file')
1186
 
        source.commit('added file')
1187
 
        build_tree(source.basis_tree(), target)
1188
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
1189
 
                          'file.moved', 'file', None, 'new-file')],
1190
 
                         target.conflicts())
1191
 
        target2 = self.make_branch_and_tree('target2')
1192
 
        target_file = file('target2/file', 'wb')
1193
 
        try:
1194
 
            source_file = file('source/file', 'rb')
1195
 
            try:
1196
 
                target_file.write(source_file.read())
1197
 
            finally:
1198
 
                source_file.close()
1199
 
        finally:
1200
 
            target_file.close()
1201
 
        build_tree(source.basis_tree(), target2)
1202
 
        self.assertEqual([], target2.conflicts())
1203
 
 
1204
 
    def test_symlink_conflict_handling(self):
1205
 
        """Ensure that when building trees, conflict handling is done"""
1206
 
        if not has_symlinks():
1207
 
            raise TestSkipped('Test requires symlink support')
1208
 
        source = self.make_branch_and_tree('source')
1209
 
        os.symlink('foo', 'source/symlink')
1210
 
        source.add('symlink', 'new-symlink')
1211
 
        source.commit('added file')
1212
 
        target = self.make_branch_and_tree('target')
1213
 
        os.symlink('bar', 'target/symlink')
1214
 
        build_tree(source.basis_tree(), target)
1215
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
1216
 
            'symlink.moved', 'symlink', None, 'new-symlink')],
1217
 
            target.conflicts())
1218
 
        target = self.make_branch_and_tree('target2')
1219
 
        os.symlink('foo', 'target2/symlink')
1220
 
        build_tree(source.basis_tree(), target)
1221
 
        self.assertEqual([], target.conflicts())
1222
731
        
1223
 
    def test_directory_conflict_handling(self):
1224
 
        """Ensure that when building trees, conflict handling is done"""
1225
 
        source = self.make_branch_and_tree('source')
1226
 
        target = self.make_branch_and_tree('target')
1227
 
        self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
1228
 
        source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
1229
 
        source.commit('added file')
1230
 
        build_tree(source.basis_tree(), target)
1231
 
        self.assertEqual([], target.conflicts())
1232
 
        self.failUnlessExists('target/dir1/file')
1233
 
 
1234
 
        # Ensure contents are merged
1235
 
        target = self.make_branch_and_tree('target2')
1236
 
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
1237
 
        build_tree(source.basis_tree(), target)
1238
 
        self.assertEqual([], target.conflicts())
1239
 
        self.failUnlessExists('target2/dir1/file2')
1240
 
        self.failUnlessExists('target2/dir1/file')
1241
 
 
1242
 
        # Ensure new contents are suppressed for existing branches
1243
 
        target = self.make_branch_and_tree('target3')
1244
 
        self.make_branch('target3/dir1')
1245
 
        self.build_tree(['target3/dir1/file2'])
1246
 
        build_tree(source.basis_tree(), target)
1247
 
        self.failIfExists('target3/dir1/file')
1248
 
        self.failUnlessExists('target3/dir1/file2')
1249
 
        self.failUnlessExists('target3/dir1.diverted/file')
1250
 
        self.assertEqual([DuplicateEntry('Diverted to',
1251
 
            'dir1.diverted', 'dir1', 'new-dir1', None)],
1252
 
            target.conflicts())
1253
 
 
1254
 
        target = self.make_branch_and_tree('target4')
1255
 
        self.build_tree(['target4/dir1/'])
1256
 
        self.make_branch('target4/dir1/file')
1257
 
        build_tree(source.basis_tree(), target)
1258
 
        self.failUnlessExists('target4/dir1/file')
1259
 
        self.assertEqual('directory', file_kind('target4/dir1/file'))
1260
 
        self.failUnlessExists('target4/dir1/file.diverted')
1261
 
        self.assertEqual([DuplicateEntry('Diverted to',
1262
 
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
1263
 
            target.conflicts())
1264
 
 
1265
 
    def test_mixed_conflict_handling(self):
1266
 
        """Ensure that when building trees, conflict handling is done"""
1267
 
        source = self.make_branch_and_tree('source')
1268
 
        target = self.make_branch_and_tree('target')
1269
 
        self.build_tree(['source/name', 'target/name/'])
1270
 
        source.add('name', 'new-name')
1271
 
        source.commit('added file')
1272
 
        build_tree(source.basis_tree(), target)
1273
 
        self.assertEqual([DuplicateEntry('Moved existing file to',
1274
 
            'name.moved', 'name', None, 'new-name')], target.conflicts())
1275
 
 
1276
 
    def test_raises_in_populated(self):
1277
 
        source = self.make_branch_and_tree('source')
1278
 
        self.build_tree(['source/name'])
1279
 
        source.add('name')
1280
 
        source.commit('added name')
1281
 
        target = self.make_branch_and_tree('target')
1282
 
        self.build_tree(['target/name'])
1283
 
        target.add('name')
1284
 
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
1285
 
            build_tree, source.basis_tree(), target)
1286
 
 
1287
 
    def test_build_tree_rename_count(self):
1288
 
        source = self.make_branch_and_tree('source')
1289
 
        self.build_tree(['source/file1', 'source/dir1/'])
1290
 
        source.add(['file1', 'dir1'])
1291
 
        source.commit('add1')
1292
 
        target1 = self.make_branch_and_tree('target1')
1293
 
        transform_result = build_tree(source.basis_tree(), target1)
1294
 
        self.assertEqual(2, transform_result.rename_count)
1295
 
 
1296
 
        self.build_tree(['source/dir1/file2'])
1297
 
        source.add(['dir1/file2'])
1298
 
        source.commit('add3')
1299
 
        target2 = self.make_branch_and_tree('target2')
1300
 
        transform_result = build_tree(source.basis_tree(), target2)
1301
 
        # children of non-root directories should not be renamed
1302
 
        self.assertEqual(2, transform_result.rename_count)
1303
 
 
1304
 
 
1305
732
class MockTransform(object):
1306
733
 
1307
734
    def has_named_child(self, by_parent, parent_id, name):
1313
740
                return True
1314
741
        return False
1315
742
 
1316
 
 
1317
743
class MockEntry(object):
1318
744
    def __init__(self):
1319
745
        object.__init__(self)