/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

(John Arbash Meinel)  Fix bug #158333,
        make sure that Repository.fetch(self) is properly a no-op for all
        Repository implementations.

Show diffs side-by-side

added added

removed removed

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