~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Canonical.com Patch Queue Manager
  • Date: 2007-10-24 12:49:17 UTC
  • mfrom: (2935.1.1 ianc-integration)
  • Revision ID: pqm@pqm.ubuntu.com-20071024124917-xb75eckyxx6vkrlg
Makefile fixes - hooks.html generation & allow python to be overridden (Ian Clatworthy)

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
    errors,
 
23
    generate_ids,
 
24
    symbol_versioning,
 
25
    tests,
 
26
    urlutils,
 
27
    )
19
28
from bzrlib.bzrdir import BzrDir
20
29
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
21
 
                              UnversionedParent, ParentLoop)
 
30
                              UnversionedParent, ParentLoop, DeletingParent,)
22
31
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
23
32
                           ReusingTransform, CantMoveRoot, 
24
33
                           PathsNotVersionedError, ExistingLimbo,
25
 
                           ImmortalLimbo, LockError)
 
34
                           ExistingPendingDeletion, ImmortalLimbo,
 
35
                           ImmortalPendingDeletion, LockError)
26
36
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
27
37
from bzrlib.merge import Merge3Merger
28
38
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
29
39
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
30
40
                              resolve_conflicts, cook_conflicts, 
31
 
                              find_interesting, build_tree, get_backup_name)
32
 
import bzrlib.urlutils as urlutils
33
 
 
34
 
class TestTreeTransform(TestCaseInTempDir):
 
41
                              find_interesting, build_tree, get_backup_name,
 
42
                              change_entry, _FileMover)
 
43
 
 
44
 
 
45
class TestTreeTransform(tests.TestCaseWithTransport):
35
46
 
36
47
    def setUp(self):
37
48
        super(TestTreeTransform, self).setUp()
38
 
        self.wt = BzrDir.create_standalone_workingtree('.')
 
49
        self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
39
50
        os.chdir('..')
40
51
 
41
52
    def get_transform(self):
42
53
        transform = TreeTransform(self.wt)
43
54
        #self.addCleanup(transform.finalize)
44
 
        return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
 
55
        return transform, transform.root
45
56
 
46
57
    def test_existing_limbo(self):
47
 
        limbo_name = urlutils.local_path_from_url(
48
 
            self.wt._control_files.controlfilename('limbo'))
49
58
        transform, root = self.get_transform()
 
59
        limbo_name = transform._limbodir
 
60
        deletion_path = transform._deletiondir
50
61
        os.mkdir(pathjoin(limbo_name, 'hehe'))
51
62
        self.assertRaises(ImmortalLimbo, transform.apply)
52
63
        self.assertRaises(LockError, self.wt.unlock)
54
65
        self.assertRaises(LockError, self.wt.unlock)
55
66
        os.rmdir(pathjoin(limbo_name, 'hehe'))
56
67
        os.rmdir(limbo_name)
 
68
        os.rmdir(deletion_path)
57
69
        transform, root = self.get_transform()
58
70
        transform.apply()
59
71
 
 
72
    def test_existing_pending_deletion(self):
 
73
        transform, root = self.get_transform()
 
74
        deletion_path = self._limbodir = urlutils.local_path_from_url(
 
75
            transform._tree._control_files.controlfilename('pending-deletion'))
 
76
        os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
 
77
        self.assertRaises(ImmortalPendingDeletion, transform.apply)
 
78
        self.assertRaises(LockError, self.wt.unlock)
 
79
        self.assertRaises(ExistingPendingDeletion, self.get_transform)
 
80
 
60
81
    def test_build(self):
61
82
        transform, root = self.get_transform() 
62
83
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
118
139
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
119
140
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
120
141
 
121
 
        self.assertEqual('toto-contents', 
 
142
        self.assertEqual('toto-contents',
122
143
                         self.wt.get_file_byname('oz/dorothy/toto').read())
123
144
        self.assertIs(self.wt.is_executable('toto-id'), False)
124
145
 
 
146
    def test_tree_reference(self):
 
147
        transform, root = self.get_transform()
 
148
        tree = transform._tree
 
149
        trans_id = transform.new_directory('reference', root, 'subtree-id')
 
150
        transform.set_tree_reference('subtree-revision', trans_id)
 
151
        transform.apply()
 
152
        tree.lock_read()
 
153
        self.addCleanup(tree.unlock)
 
154
        self.assertEqual('subtree-revision',
 
155
                         tree.inventory['subtree-id'].reference_revision)
 
156
 
125
157
    def test_conflicts(self):
126
158
        transform, root = self.get_transform()
127
159
        trans_id = transform.new_file('name', root, 'contents', 
191
223
        transform3.delete_contents(oz_id)
192
224
        self.assertEqual(transform3.find_conflicts(), 
193
225
                         [('missing parent', oz_id)])
194
 
        root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
 
226
        root_id = transform3.root
195
227
        tip_id = transform3.trans_id_tree_file_id('tip-id')
196
228
        transform3.adjust_path('tip', root_id, tip_id)
197
229
        transform3.apply()
223
255
    def test_name_invariants(self):
224
256
        create_tree, root = self.get_transform()
225
257
        # prepare tree
226
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
258
        root = create_tree.root
227
259
        create_tree.new_file('name1', root, 'hello1', 'name1')
228
260
        create_tree.new_file('name2', root, 'hello2', 'name2')
229
261
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
233
265
        create_tree.apply()
234
266
 
235
267
        mangle_tree,root = self.get_transform()
236
 
        root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
 
268
        root = mangle_tree.root
237
269
        #swap names
238
270
        name1 = mangle_tree.trans_id_tree_file_id('name1')
239
271
        name2 = mangle_tree.trans_id_tree_file_id('name2')
318
350
    def test_move_dangling_ie(self):
319
351
        create_tree, root = self.get_transform()
320
352
        # prepare tree
321
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
353
        root = create_tree.root
322
354
        create_tree.new_file('name1', root, 'hello1', 'name1')
323
355
        create_tree.apply()
324
356
        delete_contents, root = self.get_transform()
334
366
    def test_replace_dangling_ie(self):
335
367
        create_tree, root = self.get_transform()
336
368
        # prepare tree
337
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
369
        root = create_tree.root
338
370
        create_tree.new_file('name1', root, 'hello1', 'name1')
339
371
        create_tree.apply()
340
372
        delete_contents = TreeTransform(self.wt)
374
406
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
375
407
                         'wizard-target')
376
408
 
377
 
 
378
409
    def get_conflicted(self):
379
410
        create,root = self.get_transform()
380
411
        create.new_file('dorothy', root, 'dorothy', 'dorothy-id')
387
418
                                         'dorothy-id')
388
419
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
389
420
        oz = conflicts.trans_id_tree_file_id('oz-id')
390
 
        # set up missing, unversioned parent
 
421
        # set up DeletedParent parent conflict
391
422
        conflicts.delete_versioned(oz)
392
423
        emerald = conflicts.trans_id_tree_file_id('emerald-id')
 
424
        # set up MissingParent conflict
 
425
        munchkincity = conflicts.trans_id_file_id('munchkincity-id')
 
426
        conflicts.adjust_path('munchkincity', root, munchkincity)
 
427
        conflicts.new_directory('auntem', munchkincity, 'auntem-id')
393
428
        # set up parent loop
394
429
        conflicts.adjust_path('emeraldcity', emerald, emerald)
395
430
        return conflicts, emerald, oz, old_dorothy, new_dorothy
416
451
                                   'dorothy.moved', 'dorothy', None,
417
452
                                   'dorothy-id')
418
453
        self.assertEqual(cooked_conflicts[1], duplicate_id)
419
 
        missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
 
454
        missing_parent = MissingParent('Created directory', 'munchkincity',
 
455
                                       'munchkincity-id')
 
456
        deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
420
457
        self.assertEqual(cooked_conflicts[2], missing_parent)
421
 
        unversioned_parent = UnversionedParent('Versioned directory', 'oz',
 
458
        unversioned_parent = UnversionedParent('Versioned directory',
 
459
                                               'munchkincity',
 
460
                                               'munchkincity-id')
 
461
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
422
462
                                               'oz-id')
423
463
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
424
464
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
425
465
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
426
 
        self.assertEqual(cooked_conflicts[4], parent_loop)
427
 
        self.assertEqual(len(cooked_conflicts), 5)
 
466
        self.assertEqual(cooked_conflicts[4], deleted_parent)
 
467
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
 
468
        self.assertEqual(cooked_conflicts[6], parent_loop)
 
469
        self.assertEqual(len(cooked_conflicts), 7)
428
470
        tt.finalize()
429
471
 
430
472
    def test_string_conflicts(self):
440
482
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
441
483
                                         'Unversioned existing file '
442
484
                                         'dorothy.moved.')
443
 
        self.assertEqual(conflicts_s[2], 'Conflict adding files to oz.  '
444
 
                                         'Not deleting.')
445
 
        self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
446
 
                                         'oz.  Versioned directory.')
447
 
        self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
 
485
        self.assertEqual(conflicts_s[2], 'Conflict adding files to'
 
486
                                         ' munchkincity.  Created directory.')
 
487
        self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
 
488
                                         ' versioned, but has versioned'
 
489
                                         ' children.  Versioned directory.')
 
490
        self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
 
491
                                         " is not empty.  Not deleting.")
 
492
        self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
 
493
                                         ' versioned, but has versioned'
 
494
                                         ' children.  Versioned directory.')
 
495
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
448
496
                                         ' oz/emeraldcity.  Cancelled move.')
449
497
 
450
498
    def test_moving_versioned_directories(self):
491
539
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
492
540
        create.new_file('uvfile', root, 'othertext')
493
541
        create.apply()
494
 
        self.assertEqual(find_interesting(wt, wt, ['vfile']),
495
 
                         set(['myfile-id']))
496
 
        self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
497
 
                          ['uvfile'])
 
542
        result = self.applyDeprecated(symbol_versioning.zero_fifteen,
 
543
            find_interesting, wt, wt, ['vfile'])
 
544
        self.assertEqual(result, set(['myfile-id']))
498
545
 
499
546
    def test_set_executability_order(self):
500
547
        """Ensure that executability behaves the same, no matter what order.
509
556
        wt = transform._tree
510
557
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
511
558
                           True)
512
 
        sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
 
559
        sac = transform.new_file('set_after_creation', root,
 
560
                                 'Set after creation', 'sac')
513
561
        transform.set_executability(True, sac)
514
 
        uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
 
562
        uws = transform.new_file('unset_without_set', root, 'Unset badly',
 
563
                                 'uws')
515
564
        self.assertRaises(KeyError, transform.set_executability, None, uws)
516
565
        transform.apply()
517
566
        self.assertTrue(wt.is_executable('soc'))
518
567
        self.assertTrue(wt.is_executable('sac'))
519
568
 
 
569
    def test_preserve_mode(self):
 
570
        """File mode is preserved when replacing content"""
 
571
        if sys.platform == 'win32':
 
572
            raise TestSkipped('chmod has no effect on win32')
 
573
        transform, root = self.get_transform()
 
574
        transform.new_file('file1', root, 'contents', 'file1-id', True)
 
575
        transform.apply()
 
576
        self.assertTrue(self.wt.is_executable('file1-id'))
 
577
        transform, root = self.get_transform()
 
578
        file1_id = transform.trans_id_tree_file_id('file1-id')
 
579
        transform.delete_contents(file1_id)
 
580
        transform.create_file('contents2', file1_id)
 
581
        transform.apply()
 
582
        self.assertTrue(self.wt.is_executable('file1-id'))
 
583
 
 
584
    def test__set_mode_stats_correctly(self):
 
585
        """_set_mode stats to determine file mode."""
 
586
        if sys.platform == 'win32':
 
587
            raise TestSkipped('chmod has no effect on win32')
 
588
 
 
589
        stat_paths = []
 
590
        real_stat = os.stat
 
591
        def instrumented_stat(path):
 
592
            stat_paths.append(path)
 
593
            return real_stat(path)
 
594
 
 
595
        transform, root = self.get_transform()
 
596
 
 
597
        bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
 
598
                                     file_id='bar-id-1', executable=False)
 
599
        transform.apply()
 
600
 
 
601
        transform, root = self.get_transform()
 
602
        bar1_id = transform.trans_id_tree_path('bar')
 
603
        bar2_id = transform.trans_id_tree_path('bar2')
 
604
        try:
 
605
            os.stat = instrumented_stat
 
606
            transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
 
607
        finally:
 
608
            os.stat = real_stat
 
609
            transform.finalize()
 
610
 
 
611
        bar1_abspath = self.wt.abspath('bar')
 
612
        self.assertEqual([bar1_abspath], stat_paths)
 
613
 
 
614
    def test_iter_changes(self):
 
615
        self.wt.set_root_id('eert_toor')
 
616
        transform, root = self.get_transform()
 
617
        transform.new_file('old', root, 'blah', 'id-1', True)
 
618
        transform.apply()
 
619
        transform, root = self.get_transform()
 
620
        try:
 
621
            self.assertEqual([], list(transform._iter_changes()))
 
622
            old = transform.trans_id_tree_file_id('id-1')
 
623
            transform.unversion_file(old)
 
624
            self.assertEqual([('id-1', ('old', None), False, (True, False),
 
625
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
626
                (True, True))], list(transform._iter_changes()))
 
627
            transform.new_directory('new', root, 'id-1')
 
628
            self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
 
629
                ('eert_toor', 'eert_toor'), ('old', 'new'),
 
630
                ('file', 'directory'),
 
631
                (True, False))], list(transform._iter_changes()))
 
632
        finally:
 
633
            transform.finalize()
 
634
 
 
635
    def test_iter_changes_new(self):
 
636
        self.wt.set_root_id('eert_toor')
 
637
        transform, root = self.get_transform()
 
638
        transform.new_file('old', root, 'blah')
 
639
        transform.apply()
 
640
        transform, root = self.get_transform()
 
641
        try:
 
642
            old = transform.trans_id_tree_path('old')
 
643
            transform.version_file('id-1', old)
 
644
            self.assertEqual([('id-1', (None, 'old'), False, (False, True),
 
645
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
646
                (False, False))], list(transform._iter_changes()))
 
647
        finally:
 
648
            transform.finalize()
 
649
 
 
650
    def test_iter_changes_modifications(self):
 
651
        self.wt.set_root_id('eert_toor')
 
652
        transform, root = self.get_transform()
 
653
        transform.new_file('old', root, 'blah', 'id-1')
 
654
        transform.new_file('new', root, 'blah')
 
655
        transform.new_directory('subdir', root, 'subdir-id')
 
656
        transform.apply()
 
657
        transform, root = self.get_transform()
 
658
        try:
 
659
            old = transform.trans_id_tree_path('old')
 
660
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
661
            new = transform.trans_id_tree_path('new')
 
662
            self.assertEqual([], list(transform._iter_changes()))
 
663
 
 
664
            #content deletion
 
665
            transform.delete_contents(old)
 
666
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
667
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
 
668
                (False, False))], list(transform._iter_changes()))
 
669
 
 
670
            #content change
 
671
            transform.create_file('blah', old)
 
672
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
673
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
674
                (False, False))], list(transform._iter_changes()))
 
675
            transform.cancel_deletion(old)
 
676
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
677
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
678
                (False, False))], list(transform._iter_changes()))
 
679
            transform.cancel_creation(old)
 
680
 
 
681
            # move file_id to a different file
 
682
            self.assertEqual([], list(transform._iter_changes()))
 
683
            transform.unversion_file(old)
 
684
            transform.version_file('id-1', new)
 
685
            transform.adjust_path('old', root, new)
 
686
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
687
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
688
                (False, False))], list(transform._iter_changes()))
 
689
            transform.cancel_versioning(new)
 
690
            transform._removed_id = set()
 
691
 
 
692
            #execute bit
 
693
            self.assertEqual([], list(transform._iter_changes()))
 
694
            transform.set_executability(True, old)
 
695
            self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
 
696
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
697
                (False, True))], list(transform._iter_changes()))
 
698
            transform.set_executability(None, old)
 
699
 
 
700
            # filename
 
701
            self.assertEqual([], list(transform._iter_changes()))
 
702
            transform.adjust_path('new', root, old)
 
703
            transform._new_parent = {}
 
704
            self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
 
705
                ('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
 
706
                (False, False))], list(transform._iter_changes()))
 
707
            transform._new_name = {}
 
708
 
 
709
            # parent directory
 
710
            self.assertEqual([], list(transform._iter_changes()))
 
711
            transform.adjust_path('new', subdir, old)
 
712
            transform._new_name = {}
 
713
            self.assertEqual([('id-1', ('old', 'subdir/old'), False,
 
714
                (True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
 
715
                ('file', 'file'), (False, False))],
 
716
                list(transform._iter_changes()))
 
717
            transform._new_path = {}
 
718
 
 
719
        finally:
 
720
            transform.finalize()
 
721
 
 
722
    def test_iter_changes_modified_bleed(self):
 
723
        self.wt.set_root_id('eert_toor')
 
724
        """Modified flag should not bleed from one change to another"""
 
725
        # unfortunately, we have no guarantee that file1 (which is modified)
 
726
        # will be applied before file2.  And if it's applied after file2, it
 
727
        # obviously can't bleed into file2's change output.  But for now, it
 
728
        # works.
 
729
        transform, root = self.get_transform()
 
730
        transform.new_file('file1', root, 'blah', 'id-1')
 
731
        transform.new_file('file2', root, 'blah', 'id-2')
 
732
        transform.apply()
 
733
        transform, root = self.get_transform()
 
734
        try:
 
735
            transform.delete_contents(transform.trans_id_file_id('id-1'))
 
736
            transform.set_executability(True,
 
737
            transform.trans_id_file_id('id-2'))
 
738
            self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
 
739
                ('eert_toor', 'eert_toor'), ('file1', u'file1'),
 
740
                ('file', None), (False, False)),
 
741
                ('id-2', (u'file2', u'file2'), False, (True, True),
 
742
                ('eert_toor', 'eert_toor'), ('file2', u'file2'),
 
743
                ('file', 'file'), (False, True))],
 
744
                list(transform._iter_changes()))
 
745
        finally:
 
746
            transform.finalize()
 
747
 
 
748
    def test_iter_changes_move_missing(self):
 
749
        """Test moving ids with no files around"""
 
750
        self.wt.set_root_id('toor_eert')
 
751
        # Need two steps because versioning a non-existant file is a conflict.
 
752
        transform, root = self.get_transform()
 
753
        transform.new_directory('floater', root, 'floater-id')
 
754
        transform.apply()
 
755
        transform, root = self.get_transform()
 
756
        transform.delete_contents(transform.trans_id_tree_path('floater'))
 
757
        transform.apply()
 
758
        transform, root = self.get_transform()
 
759
        floater = transform.trans_id_tree_path('floater')
 
760
        try:
 
761
            transform.adjust_path('flitter', root, floater)
 
762
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
 
763
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
 
764
            (None, None), (False, False))], list(transform._iter_changes()))
 
765
        finally:
 
766
            transform.finalize()
 
767
 
 
768
    def test_iter_changes_pointless(self):
 
769
        """Ensure that no-ops are not treated as modifications"""
 
770
        self.wt.set_root_id('eert_toor')
 
771
        transform, root = self.get_transform()
 
772
        transform.new_file('old', root, 'blah', 'id-1')
 
773
        transform.new_directory('subdir', root, 'subdir-id')
 
774
        transform.apply()
 
775
        transform, root = self.get_transform()
 
776
        try:
 
777
            old = transform.trans_id_tree_path('old')
 
778
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
779
            self.assertEqual([], list(transform._iter_changes()))
 
780
            transform.delete_contents(subdir)
 
781
            transform.create_directory(subdir)
 
782
            transform.set_executability(False, old)
 
783
            transform.unversion_file(old)
 
784
            transform.version_file('id-1', old)
 
785
            transform.adjust_path('old', root, old)
 
786
            self.assertEqual([], list(transform._iter_changes()))
 
787
        finally:
 
788
            transform.finalize()
 
789
 
 
790
    def test_rename_count(self):
 
791
        transform, root = self.get_transform()
 
792
        transform.new_file('name1', root, 'contents')
 
793
        self.assertEqual(transform.rename_count, 0)
 
794
        transform.apply()
 
795
        self.assertEqual(transform.rename_count, 1)
 
796
        transform2, root = self.get_transform()
 
797
        transform2.adjust_path('name2', root,
 
798
                               transform2.trans_id_tree_path('name1'))
 
799
        self.assertEqual(transform2.rename_count, 0)
 
800
        transform2.apply()
 
801
        self.assertEqual(transform2.rename_count, 2)
 
802
 
 
803
    def test_change_parent(self):
 
804
        """Ensure that after we change a parent, the results are still right.
 
805
 
 
806
        Renames and parent changes on pending transforms can happen as part
 
807
        of conflict resolution, and are explicitly permitted by the
 
808
        TreeTransform API.
 
809
 
 
810
        This test ensures they work correctly with the rename-avoidance
 
811
        optimization.
 
812
        """
 
813
        transform, root = self.get_transform()
 
814
        parent1 = transform.new_directory('parent1', root)
 
815
        child1 = transform.new_file('child1', parent1, 'contents')
 
816
        parent2 = transform.new_directory('parent2', root)
 
817
        transform.adjust_path('child1', parent2, child1)
 
818
        transform.apply()
 
819
        self.failIfExists(self.wt.abspath('parent1/child1'))
 
820
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
821
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
 
822
        # no rename for child1 (counting only renames during apply)
 
823
        self.failUnlessEqual(2, transform.rename_count)
 
824
 
 
825
    def test_cancel_parent(self):
 
826
        """Cancelling a parent doesn't cause deletion of a non-empty directory
 
827
 
 
828
        This is like the test_change_parent, except that we cancel the parent
 
829
        before adjusting the path.  The transform must detect that the
 
830
        directory is non-empty, and move children to safe locations.
 
831
        """
 
832
        transform, root = self.get_transform()
 
833
        parent1 = transform.new_directory('parent1', root)
 
834
        child1 = transform.new_file('child1', parent1, 'contents')
 
835
        child2 = transform.new_file('child2', parent1, 'contents')
 
836
        try:
 
837
            transform.cancel_creation(parent1)
 
838
        except OSError:
 
839
            self.fail('Failed to move child1 before deleting parent1')
 
840
        transform.cancel_creation(child2)
 
841
        transform.create_directory(parent1)
 
842
        try:
 
843
            transform.cancel_creation(parent1)
 
844
        # If the transform incorrectly believes that child2 is still in
 
845
        # parent1's limbo directory, it will try to rename it and fail
 
846
        # because was already moved by the first cancel_creation.
 
847
        except OSError:
 
848
            self.fail('Transform still thinks child2 is a child of parent1')
 
849
        parent2 = transform.new_directory('parent2', root)
 
850
        transform.adjust_path('child1', parent2, child1)
 
851
        transform.apply()
 
852
        self.failIfExists(self.wt.abspath('parent1'))
 
853
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
854
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
 
855
        self.failUnlessEqual(2, transform.rename_count)
 
856
 
 
857
    def test_adjust_and_cancel(self):
 
858
        """Make sure adjust_path keeps track of limbo children properly"""
 
859
        transform, root = self.get_transform()
 
860
        parent1 = transform.new_directory('parent1', root)
 
861
        child1 = transform.new_file('child1', parent1, 'contents')
 
862
        parent2 = transform.new_directory('parent2', root)
 
863
        transform.adjust_path('child1', parent2, child1)
 
864
        transform.cancel_creation(child1)
 
865
        try:
 
866
            transform.cancel_creation(parent1)
 
867
        # if the transform thinks child1 is still in parent1's limbo
 
868
        # directory, it will attempt to move it and fail.
 
869
        except OSError:
 
870
            self.fail('Transform still thinks child1 is a child of parent1')
 
871
        transform.finalize()
 
872
 
 
873
    def test_noname_contents(self):
 
874
        """TreeTransform should permit deferring naming files."""
 
875
        transform, root = self.get_transform()
 
876
        parent = transform.trans_id_file_id('parent-id')
 
877
        try:
 
878
            transform.create_directory(parent)
 
879
        except KeyError:
 
880
            self.fail("Can't handle contents with no name")
 
881
        transform.finalize()
 
882
 
 
883
    def test_noname_contents_nested(self):
 
884
        """TreeTransform should permit deferring naming files."""
 
885
        transform, root = self.get_transform()
 
886
        parent = transform.trans_id_file_id('parent-id')
 
887
        try:
 
888
            transform.create_directory(parent)
 
889
        except KeyError:
 
890
            self.fail("Can't handle contents with no name")
 
891
        child = transform.new_directory('child', parent)
 
892
        transform.adjust_path('parent', root, parent)
 
893
        transform.apply()
 
894
        self.failUnlessExists(self.wt.abspath('parent/child'))
 
895
        self.assertEqual(1, transform.rename_count)
 
896
 
 
897
    def test_reuse_name(self):
 
898
        """Avoid reusing the same limbo name for different files"""
 
899
        transform, root = self.get_transform()
 
900
        parent = transform.new_directory('parent', root)
 
901
        child1 = transform.new_directory('child', parent)
 
902
        try:
 
903
            child2 = transform.new_directory('child', parent)
 
904
        except OSError:
 
905
            self.fail('Tranform tried to use the same limbo name twice')
 
906
        transform.adjust_path('child2', parent, child2)
 
907
        transform.apply()
 
908
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
 
909
        # child2 is put into top-level limbo because child1 has already
 
910
        # claimed the direct limbo path when child2 is created.  There is no
 
911
        # advantage in renaming files once they're in top-level limbo, except
 
912
        # as part of apply.
 
913
        self.assertEqual(2, transform.rename_count)
 
914
 
 
915
    def test_reuse_when_first_moved(self):
 
916
        """Don't avoid direct paths when it is safe to use them"""
 
917
        transform, root = self.get_transform()
 
918
        parent = transform.new_directory('parent', root)
 
919
        child1 = transform.new_directory('child', parent)
 
920
        transform.adjust_path('child1', parent, child1)
 
921
        child2 = transform.new_directory('child', parent)
 
922
        transform.apply()
 
923
        # limbo/new-1 => parent
 
924
        self.assertEqual(1, transform.rename_count)
 
925
 
 
926
    def test_reuse_after_cancel(self):
 
927
        """Don't avoid direct paths when it is safe to use them"""
 
928
        transform, root = self.get_transform()
 
929
        parent2 = transform.new_directory('parent2', root)
 
930
        child1 = transform.new_directory('child1', parent2)
 
931
        transform.cancel_creation(parent2)
 
932
        transform.create_directory(parent2)
 
933
        child2 = transform.new_directory('child1', parent2)
 
934
        transform.adjust_path('child2', parent2, child1)
 
935
        transform.apply()
 
936
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
 
937
        self.assertEqual(2, transform.rename_count)
 
938
 
 
939
    def test_finalize_order(self):
 
940
        """Finalize must be done in child-to-parent order"""
 
941
        transform, root = self.get_transform()
 
942
        parent = transform.new_directory('parent', root)
 
943
        child = transform.new_directory('child', parent)
 
944
        try:
 
945
            transform.finalize()
 
946
        except OSError:
 
947
            self.fail('Tried to remove parent before child1')
 
948
 
 
949
    def test_cancel_with_cancelled_child_should_succeed(self):
 
950
        transform, root = self.get_transform()
 
951
        parent = transform.new_directory('parent', root)
 
952
        child = transform.new_directory('child', parent)
 
953
        transform.cancel_creation(child)
 
954
        transform.cancel_creation(parent)
 
955
        transform.finalize()
 
956
 
 
957
    def test_change_entry(self):
 
958
        txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
 
959
        self.callDeprecated([txt], change_entry, None, None, None, None, None,
 
960
            None, None, None)
 
961
 
520
962
 
521
963
class TransformGroup(object):
522
 
    def __init__(self, dirname):
 
964
    def __init__(self, dirname, root_id):
523
965
        self.name = dirname
524
966
        os.mkdir(dirname)
525
967
        self.wt = BzrDir.create_standalone_workingtree(dirname)
 
968
        self.wt.set_root_id(root_id)
526
969
        self.b = self.wt.branch
527
970
        self.tt = TreeTransform(self.wt)
528
971
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
529
972
 
 
973
 
530
974
def conflict_text(tree, merge):
531
975
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
532
976
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
534
978
 
535
979
class TestTransformMerge(TestCaseInTempDir):
536
980
    def test_text_merge(self):
537
 
        base = TransformGroup("base")
 
981
        root_id = generate_ids.gen_root_id()
 
982
        base = TransformGroup("base", root_id)
538
983
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
539
984
        base.tt.new_file('b', base.root, 'b1', 'b')
540
985
        base.tt.new_file('c', base.root, 'c', 'c')
544
989
        base.tt.new_directory('g', base.root, 'g')
545
990
        base.tt.new_directory('h', base.root, 'h')
546
991
        base.tt.apply()
547
 
        other = TransformGroup("other")
 
992
        other = TransformGroup("other", root_id)
548
993
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
549
994
        other.tt.new_file('b', other.root, 'b2', 'b')
550
995
        other.tt.new_file('c', other.root, 'c2', 'c')
555
1000
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
556
1001
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
557
1002
        other.tt.apply()
558
 
        this = TransformGroup("this")
 
1003
        this = TransformGroup("this", root_id)
559
1004
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
560
1005
        this.tt.new_file('b', this.root, 'b', 'b')
561
1006
        this.tt.new_file('c', this.root, 'c', 'c')
607
1052
        self.assertSubset(merge_modified, modified)
608
1053
        self.assertEqual(len(merge_modified), len(modified))
609
1054
        this.wt.remove('b')
610
 
        this.wt.revert([])
 
1055
        this.wt.revert()
611
1056
 
612
1057
    def test_file_merge(self):
613
1058
        if not has_symlinks():
614
1059
            raise TestSkipped('Symlinks are not supported on this platform')
615
 
        base = TransformGroup("BASE")
616
 
        this = TransformGroup("THIS")
617
 
        other = TransformGroup("OTHER")
 
1060
        root_id = generate_ids.gen_root_id()
 
1061
        base = TransformGroup("BASE", root_id)
 
1062
        this = TransformGroup("THIS", root_id)
 
1063
        other = TransformGroup("OTHER", root_id)
618
1064
        for tg in this, base, other:
619
1065
            tg.tt.new_directory('a', tg.root, 'a')
620
1066
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
652
1098
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
653
1099
 
654
1100
    def test_filename_merge(self):
655
 
        base = TransformGroup("BASE")
656
 
        this = TransformGroup("THIS")
657
 
        other = TransformGroup("OTHER")
 
1101
        root_id = generate_ids.gen_root_id()
 
1102
        base = TransformGroup("BASE", root_id)
 
1103
        this = TransformGroup("THIS", root_id)
 
1104
        other = TransformGroup("OTHER", root_id)
658
1105
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
659
1106
                                   for t in [base, this, other]]
660
1107
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
684
1131
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
685
1132
 
686
1133
    def test_filename_merge_conflicts(self):
687
 
        base = TransformGroup("BASE")
688
 
        this = TransformGroup("THIS")
689
 
        other = TransformGroup("OTHER")
 
1134
        root_id = generate_ids.gen_root_id()
 
1135
        base = TransformGroup("BASE", root_id)
 
1136
        this = TransformGroup("THIS", root_id)
 
1137
        other = TransformGroup("OTHER", root_id)
690
1138
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
691
1139
                                   for t in [base, this, other]]
692
1140
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
713
1161
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
714
1162
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
715
1163
 
716
 
class TestBuildTree(TestCaseInTempDir):
 
1164
 
 
1165
class TestBuildTree(tests.TestCaseWithTransport):
 
1166
 
717
1167
    def test_build_tree(self):
718
1168
        if not has_symlinks():
719
1169
            raise TestSkipped('Test requires symlink support')
725
1175
        a.add(['foo', 'foo/bar', 'foo/baz'])
726
1176
        a.commit('initial commit')
727
1177
        b = BzrDir.create_standalone_workingtree('b')
728
 
        build_tree(a.basis_tree(), b)
 
1178
        basis = a.basis_tree()
 
1179
        basis.lock_read()
 
1180
        self.addCleanup(basis.unlock)
 
1181
        build_tree(basis, b)
729
1182
        self.assertIs(os.path.isdir('b/foo'), True)
730
1183
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
731
1184
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
 
1185
 
 
1186
    def test_build_with_references(self):
 
1187
        tree = self.make_branch_and_tree('source',
 
1188
            format='dirstate-with-subtree')
 
1189
        subtree = self.make_branch_and_tree('source/subtree',
 
1190
            format='dirstate-with-subtree')
 
1191
        tree.add_reference(subtree)
 
1192
        tree.commit('a revision')
 
1193
        tree.branch.create_checkout('target')
 
1194
        self.failUnlessExists('target')
 
1195
        self.failUnlessExists('target/subtree')
 
1196
 
 
1197
    def test_file_conflict_handling(self):
 
1198
        """Ensure that when building trees, conflict handling is done"""
 
1199
        source = self.make_branch_and_tree('source')
 
1200
        target = self.make_branch_and_tree('target')
 
1201
        self.build_tree(['source/file', 'target/file'])
 
1202
        source.add('file', 'new-file')
 
1203
        source.commit('added file')
 
1204
        build_tree(source.basis_tree(), target)
 
1205
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1206
                          'file.moved', 'file', None, 'new-file')],
 
1207
                         target.conflicts())
 
1208
        target2 = self.make_branch_and_tree('target2')
 
1209
        target_file = file('target2/file', 'wb')
 
1210
        try:
 
1211
            source_file = file('source/file', 'rb')
 
1212
            try:
 
1213
                target_file.write(source_file.read())
 
1214
            finally:
 
1215
                source_file.close()
 
1216
        finally:
 
1217
            target_file.close()
 
1218
        build_tree(source.basis_tree(), target2)
 
1219
        self.assertEqual([], target2.conflicts())
 
1220
 
 
1221
    def test_symlink_conflict_handling(self):
 
1222
        """Ensure that when building trees, conflict handling is done"""
 
1223
        if not has_symlinks():
 
1224
            raise TestSkipped('Test requires symlink support')
 
1225
        source = self.make_branch_and_tree('source')
 
1226
        os.symlink('foo', 'source/symlink')
 
1227
        source.add('symlink', 'new-symlink')
 
1228
        source.commit('added file')
 
1229
        target = self.make_branch_and_tree('target')
 
1230
        os.symlink('bar', 'target/symlink')
 
1231
        build_tree(source.basis_tree(), target)
 
1232
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1233
            'symlink.moved', 'symlink', None, 'new-symlink')],
 
1234
            target.conflicts())
 
1235
        target = self.make_branch_and_tree('target2')
 
1236
        os.symlink('foo', 'target2/symlink')
 
1237
        build_tree(source.basis_tree(), target)
 
1238
        self.assertEqual([], target.conflicts())
732
1239
        
 
1240
    def test_directory_conflict_handling(self):
 
1241
        """Ensure that when building trees, conflict handling is done"""
 
1242
        source = self.make_branch_and_tree('source')
 
1243
        target = self.make_branch_and_tree('target')
 
1244
        self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
 
1245
        source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
 
1246
        source.commit('added file')
 
1247
        build_tree(source.basis_tree(), target)
 
1248
        self.assertEqual([], target.conflicts())
 
1249
        self.failUnlessExists('target/dir1/file')
 
1250
 
 
1251
        # Ensure contents are merged
 
1252
        target = self.make_branch_and_tree('target2')
 
1253
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
 
1254
        build_tree(source.basis_tree(), target)
 
1255
        self.assertEqual([], target.conflicts())
 
1256
        self.failUnlessExists('target2/dir1/file2')
 
1257
        self.failUnlessExists('target2/dir1/file')
 
1258
 
 
1259
        # Ensure new contents are suppressed for existing branches
 
1260
        target = self.make_branch_and_tree('target3')
 
1261
        self.make_branch('target3/dir1')
 
1262
        self.build_tree(['target3/dir1/file2'])
 
1263
        build_tree(source.basis_tree(), target)
 
1264
        self.failIfExists('target3/dir1/file')
 
1265
        self.failUnlessExists('target3/dir1/file2')
 
1266
        self.failUnlessExists('target3/dir1.diverted/file')
 
1267
        self.assertEqual([DuplicateEntry('Diverted to',
 
1268
            'dir1.diverted', 'dir1', 'new-dir1', None)],
 
1269
            target.conflicts())
 
1270
 
 
1271
        target = self.make_branch_and_tree('target4')
 
1272
        self.build_tree(['target4/dir1/'])
 
1273
        self.make_branch('target4/dir1/file')
 
1274
        build_tree(source.basis_tree(), target)
 
1275
        self.failUnlessExists('target4/dir1/file')
 
1276
        self.assertEqual('directory', file_kind('target4/dir1/file'))
 
1277
        self.failUnlessExists('target4/dir1/file.diverted')
 
1278
        self.assertEqual([DuplicateEntry('Diverted to',
 
1279
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
 
1280
            target.conflicts())
 
1281
 
 
1282
    def test_mixed_conflict_handling(self):
 
1283
        """Ensure that when building trees, conflict handling is done"""
 
1284
        source = self.make_branch_and_tree('source')
 
1285
        target = self.make_branch_and_tree('target')
 
1286
        self.build_tree(['source/name', 'target/name/'])
 
1287
        source.add('name', 'new-name')
 
1288
        source.commit('added file')
 
1289
        build_tree(source.basis_tree(), target)
 
1290
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1291
            'name.moved', 'name', None, 'new-name')], target.conflicts())
 
1292
 
 
1293
    def test_raises_in_populated(self):
 
1294
        source = self.make_branch_and_tree('source')
 
1295
        self.build_tree(['source/name'])
 
1296
        source.add('name')
 
1297
        source.commit('added name')
 
1298
        target = self.make_branch_and_tree('target')
 
1299
        self.build_tree(['target/name'])
 
1300
        target.add('name')
 
1301
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
 
1302
            build_tree, source.basis_tree(), target)
 
1303
 
 
1304
    def test_build_tree_rename_count(self):
 
1305
        source = self.make_branch_and_tree('source')
 
1306
        self.build_tree(['source/file1', 'source/dir1/'])
 
1307
        source.add(['file1', 'dir1'])
 
1308
        source.commit('add1')
 
1309
        target1 = self.make_branch_and_tree('target1')
 
1310
        transform_result = build_tree(source.basis_tree(), target1)
 
1311
        self.assertEqual(2, transform_result.rename_count)
 
1312
 
 
1313
        self.build_tree(['source/dir1/file2'])
 
1314
        source.add(['dir1/file2'])
 
1315
        source.commit('add3')
 
1316
        target2 = self.make_branch_and_tree('target2')
 
1317
        transform_result = build_tree(source.basis_tree(), target2)
 
1318
        # children of non-root directories should not be renamed
 
1319
        self.assertEqual(2, transform_result.rename_count)
 
1320
 
 
1321
 
733
1322
class MockTransform(object):
734
1323
 
735
1324
    def has_named_child(self, by_parent, parent_id, name):
741
1330
                return True
742
1331
        return False
743
1332
 
 
1333
 
744
1334
class MockEntry(object):
745
1335
    def __init__(self):
746
1336
        object.__init__(self)
759
1349
        self.assertEqual(name, 'name.~1~')
760
1350
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
761
1351
        self.assertEqual(name, 'name.~4~')
 
1352
 
 
1353
 
 
1354
class TestFileMover(tests.TestCaseWithTransport):
 
1355
 
 
1356
    def test_file_mover(self):
 
1357
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
 
1358
        mover = _FileMover()
 
1359
        mover.rename('a', 'q')
 
1360
        self.failUnlessExists('q')
 
1361
        self.failIfExists('a')
 
1362
        self.failUnlessExists('q/b')
 
1363
        self.failUnlessExists('c')
 
1364
        self.failUnlessExists('c/d')
 
1365
 
 
1366
    def test_pre_delete_rollback(self):
 
1367
        self.build_tree(['a/'])
 
1368
        mover = _FileMover()
 
1369
        mover.pre_delete('a', 'q')
 
1370
        self.failUnlessExists('q')
 
1371
        self.failIfExists('a')
 
1372
        mover.rollback()
 
1373
        self.failIfExists('q')
 
1374
        self.failUnlessExists('a')
 
1375
 
 
1376
    def test_apply_deletions(self):
 
1377
        self.build_tree(['a/', 'b/'])
 
1378
        mover = _FileMover()
 
1379
        mover.pre_delete('a', 'q')
 
1380
        mover.pre_delete('b', 'r')
 
1381
        self.failUnlessExists('q')
 
1382
        self.failUnlessExists('r')
 
1383
        self.failIfExists('a')
 
1384
        self.failIfExists('b')
 
1385
        mover.apply_deletions()
 
1386
        self.failIfExists('q')
 
1387
        self.failIfExists('r')
 
1388
        self.failIfExists('a')
 
1389
        self.failIfExists('b')
 
1390
 
 
1391
    def test_file_mover_rollback(self):
 
1392
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
 
1393
        mover = _FileMover()
 
1394
        mover.rename('c/d', 'c/f')
 
1395
        mover.rename('c/e', 'c/d')
 
1396
        try:
 
1397
            mover.rename('a', 'c')
 
1398
        except OSError, e:
 
1399
            mover.rollback()
 
1400
        self.failUnlessExists('a')
 
1401
        self.failUnlessExists('c/d')
 
1402
 
 
1403
 
 
1404
class Bogus(Exception):
 
1405
    pass
 
1406
 
 
1407
 
 
1408
class TestTransformRollback(tests.TestCaseWithTransport):
 
1409
 
 
1410
    class ExceptionFileMover(_FileMover):
 
1411
 
 
1412
        def __init__(self, bad_source=None, bad_target=None):
 
1413
            _FileMover.__init__(self)
 
1414
            self.bad_source = bad_source
 
1415
            self.bad_target = bad_target
 
1416
 
 
1417
        def rename(self, source, target):
 
1418
            if (self.bad_source is not None and
 
1419
                source.endswith(self.bad_source)):
 
1420
                raise Bogus
 
1421
            elif (self.bad_target is not None and
 
1422
                target.endswith(self.bad_target)):
 
1423
                raise Bogus
 
1424
            else:
 
1425
                _FileMover.rename(self, source, target)
 
1426
 
 
1427
    def test_rollback_rename(self):
 
1428
        tree = self.make_branch_and_tree('.')
 
1429
        self.build_tree(['a/', 'a/b'])
 
1430
        tt = TreeTransform(tree)
 
1431
        self.addCleanup(tt.finalize)
 
1432
        a_id = tt.trans_id_tree_path('a')
 
1433
        tt.adjust_path('c', tt.root, a_id)
 
1434
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1435
        self.assertRaises(Bogus, tt.apply,
 
1436
                          _mover=self.ExceptionFileMover(bad_source='a'))
 
1437
        self.failUnlessExists('a')
 
1438
        self.failUnlessExists('a/b')
 
1439
        tt.apply()
 
1440
        self.failUnlessExists('c')
 
1441
        self.failUnlessExists('c/d')
 
1442
 
 
1443
    def test_rollback_rename_into_place(self):
 
1444
        tree = self.make_branch_and_tree('.')
 
1445
        self.build_tree(['a/', 'a/b'])
 
1446
        tt = TreeTransform(tree)
 
1447
        self.addCleanup(tt.finalize)
 
1448
        a_id = tt.trans_id_tree_path('a')
 
1449
        tt.adjust_path('c', tt.root, a_id)
 
1450
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1451
        self.assertRaises(Bogus, tt.apply,
 
1452
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
 
1453
        self.failUnlessExists('a')
 
1454
        self.failUnlessExists('a/b')
 
1455
        tt.apply()
 
1456
        self.failUnlessExists('c')
 
1457
        self.failUnlessExists('c/d')
 
1458
 
 
1459
    def test_rollback_deletion(self):
 
1460
        tree = self.make_branch_and_tree('.')
 
1461
        self.build_tree(['a/', 'a/b'])
 
1462
        tt = TreeTransform(tree)
 
1463
        self.addCleanup(tt.finalize)
 
1464
        a_id = tt.trans_id_tree_path('a')
 
1465
        tt.delete_contents(a_id)
 
1466
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
 
1467
        self.assertRaises(Bogus, tt.apply,
 
1468
                          _mover=self.ExceptionFileMover(bad_target='d'))
 
1469
        self.failUnlessExists('a')
 
1470
        self.failUnlessExists('a/b')