~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Alexander Belchenko
  • Date: 2007-01-30 23:05:35 UTC
  • mto: This revision was merged to the branch mainline in revision 2259.
  • Revision ID: bialix@ukr.net-20070130230535-kx1rd478rtigyc3v
standalone installer: win98 support

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
 
17
17
import os
18
18
import stat
19
 
from StringIO import StringIO
20
19
import sys
21
20
 
22
21
from bzrlib import (
23
22
    errors,
24
23
    generate_ids,
25
 
    osutils,
26
 
    progress,
27
 
    revision as _mod_revision,
28
 
    symbol_versioning,
29
24
    tests,
30
25
    urlutils,
31
26
    )
32
27
from bzrlib.bzrdir import BzrDir
33
28
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
34
 
                              UnversionedParent, ParentLoop, DeletingParent,
35
 
                              NonDirectoryParent)
36
 
from bzrlib.diff import show_diff_trees
 
29
                              UnversionedParent, ParentLoop, DeletingParent,)
37
30
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
38
31
                           ReusingTransform, CantMoveRoot, 
39
32
                           PathsNotVersionedError, ExistingLimbo,
40
 
                           ExistingPendingDeletion, ImmortalLimbo,
41
 
                           ImmortalPendingDeletion, LockError)
42
 
from bzrlib.osutils import file_kind, pathjoin
 
33
                           ImmortalLimbo, LockError)
 
34
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
43
35
from bzrlib.merge import Merge3Merger
44
 
from bzrlib.tests import (
45
 
    CaseInsensitiveFilesystemFeature,
46
 
    HardlinkFeature,
47
 
    SymlinkFeature,
48
 
    TestCase,
49
 
    TestCaseInTempDir,
50
 
    TestSkipped,
51
 
    )
 
36
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
52
37
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
53
38
                              resolve_conflicts, cook_conflicts, 
54
 
                              find_interesting, build_tree, get_backup_name,
55
 
                              change_entry, _FileMover, resolve_checkout,
56
 
                              TransformPreview)
57
 
 
58
 
class TestTreeTransform(tests.TestCaseWithTransport):
 
39
                              find_interesting, build_tree, get_backup_name)
 
40
 
 
41
 
 
42
class TestTreeTransform(TestCaseInTempDir):
59
43
 
60
44
    def setUp(self):
61
45
        super(TestTreeTransform, self).setUp()
62
 
        self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
 
46
        self.wt = BzrDir.create_standalone_workingtree('.')
63
47
        os.chdir('..')
64
48
 
65
49
    def get_transform(self):
68
52
        return transform, transform.root
69
53
 
70
54
    def test_existing_limbo(self):
 
55
        limbo_name = urlutils.local_path_from_url(
 
56
            self.wt._control_files.controlfilename('limbo'))
71
57
        transform, root = self.get_transform()
72
 
        limbo_name = transform._limbodir
73
 
        deletion_path = transform._deletiondir
74
58
        os.mkdir(pathjoin(limbo_name, 'hehe'))
75
59
        self.assertRaises(ImmortalLimbo, transform.apply)
76
60
        self.assertRaises(LockError, self.wt.unlock)
78
62
        self.assertRaises(LockError, self.wt.unlock)
79
63
        os.rmdir(pathjoin(limbo_name, 'hehe'))
80
64
        os.rmdir(limbo_name)
81
 
        os.rmdir(deletion_path)
82
65
        transform, root = self.get_transform()
83
66
        transform.apply()
84
67
 
85
 
    def test_existing_pending_deletion(self):
86
 
        transform, root = self.get_transform()
87
 
        deletion_path = self._limbodir = urlutils.local_path_from_url(
88
 
            transform._tree._control_files.controlfilename('pending-deletion'))
89
 
        os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
90
 
        self.assertRaises(ImmortalPendingDeletion, transform.apply)
91
 
        self.assertRaises(LockError, self.wt.unlock)
92
 
        self.assertRaises(ExistingPendingDeletion, self.get_transform)
93
 
 
94
68
    def test_build(self):
95
 
        transform, root = self.get_transform()
96
 
        self.wt.lock_tree_write()
97
 
        self.addCleanup(self.wt.unlock)
 
69
        transform, root = self.get_transform() 
98
70
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
99
71
        imaginary_id = transform.trans_id_tree_path('imaginary')
100
72
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
135
107
        transform.finalize()
136
108
        transform.finalize()
137
109
 
138
 
    def test_hardlink(self):
139
 
        self.requireFeature(HardlinkFeature)
140
 
        transform, root = self.get_transform()
141
 
        transform.new_file('file1', root, 'contents')
142
 
        transform.apply()
143
 
        target = self.make_branch_and_tree('target')
144
 
        target_transform = TreeTransform(target)
145
 
        trans_id = target_transform.create_path('file1', target_transform.root)
146
 
        target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
147
 
        target_transform.apply()
148
 
        self.failUnlessExists('target/file1')
149
 
        source_stat = os.stat(self.wt.abspath('file1'))
150
 
        target_stat = os.stat('target/file1')
151
 
        self.assertEqual(source_stat, target_stat)
152
 
 
153
110
    def test_convenience(self):
154
111
        transform, root = self.get_transform()
155
 
        self.wt.lock_tree_write()
156
 
        self.addCleanup(self.wt.unlock)
157
112
        trans_id = transform.new_file('name', root, 'contents', 
158
113
                                      'my_pretties', True)
159
114
        oz = transform.new_directory('oz', root, 'oz-id')
171
126
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
172
127
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
173
128
 
174
 
        self.assertEqual('toto-contents',
 
129
        self.assertEqual('toto-contents', 
175
130
                         self.wt.get_file_byname('oz/dorothy/toto').read())
176
131
        self.assertIs(self.wt.is_executable('toto-id'), False)
177
132
 
178
 
    def test_tree_reference(self):
179
 
        transform, root = self.get_transform()
180
 
        tree = transform._tree
181
 
        trans_id = transform.new_directory('reference', root, 'subtree-id')
182
 
        transform.set_tree_reference('subtree-revision', trans_id)
183
 
        transform.apply()
184
 
        tree.lock_read()
185
 
        self.addCleanup(tree.unlock)
186
 
        self.assertEqual('subtree-revision',
187
 
                         tree.inventory['subtree-id'].reference_revision)
188
 
 
189
133
    def test_conflicts(self):
190
134
        transform, root = self.get_transform()
191
135
        trans_id = transform.new_file('name', root, 'contents', 
260
204
        transform3.adjust_path('tip', root_id, tip_id)
261
205
        transform3.apply()
262
206
 
263
 
    def test_conflict_on_case_insensitive(self):
264
 
        tree = self.make_branch_and_tree('tree')
265
 
        # Don't try this at home, kids!
266
 
        # Force the tree to report that it is case sensitive, for conflict
267
 
        # resolution tests
268
 
        tree.case_sensitive = True
269
 
        transform = TreeTransform(tree)
270
 
        self.addCleanup(transform.finalize)
271
 
        transform.new_file('file', transform.root, 'content')
272
 
        transform.new_file('FiLe', transform.root, 'content')
273
 
        result = transform.find_conflicts()
274
 
        self.assertEqual([], result)
275
 
        transform.finalize()
276
 
        # Force the tree to report that it is case insensitive, for conflict
277
 
        # generation tests
278
 
        tree.case_sensitive = False
279
 
        transform = TreeTransform(tree)
280
 
        self.addCleanup(transform.finalize)
281
 
        transform.new_file('file', transform.root, 'content')
282
 
        transform.new_file('FiLe', transform.root, 'content')
283
 
        result = transform.find_conflicts()
284
 
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
285
 
 
286
 
    def test_conflict_on_case_insensitive_existing(self):
287
 
        tree = self.make_branch_and_tree('tree')
288
 
        self.build_tree(['tree/FiLe'])
289
 
        # Don't try this at home, kids!
290
 
        # Force the tree to report that it is case sensitive, for conflict
291
 
        # resolution tests
292
 
        tree.case_sensitive = True
293
 
        transform = TreeTransform(tree)
294
 
        self.addCleanup(transform.finalize)
295
 
        transform.new_file('file', transform.root, 'content')
296
 
        result = transform.find_conflicts()
297
 
        self.assertEqual([], result)
298
 
        transform.finalize()
299
 
        # Force the tree to report that it is case insensitive, for conflict
300
 
        # generation tests
301
 
        tree.case_sensitive = False
302
 
        transform = TreeTransform(tree)
303
 
        self.addCleanup(transform.finalize)
304
 
        transform.new_file('file', transform.root, 'content')
305
 
        result = transform.find_conflicts()
306
 
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
307
 
 
308
 
    def test_resolve_case_insensitive_conflict(self):
309
 
        tree = self.make_branch_and_tree('tree')
310
 
        # Don't try this at home, kids!
311
 
        # Force the tree to report that it is case insensitive, for conflict
312
 
        # resolution tests
313
 
        tree.case_sensitive = False
314
 
        transform = TreeTransform(tree)
315
 
        self.addCleanup(transform.finalize)
316
 
        transform.new_file('file', transform.root, 'content')
317
 
        transform.new_file('FiLe', transform.root, 'content')
318
 
        resolve_conflicts(transform)
319
 
        transform.apply()
320
 
        self.failUnlessExists('tree/file')
321
 
        self.failUnlessExists('tree/FiLe.moved')
322
 
 
323
 
    def test_resolve_checkout_case_conflict(self):
324
 
        tree = self.make_branch_and_tree('tree')
325
 
        # Don't try this at home, kids!
326
 
        # Force the tree to report that it is case insensitive, for conflict
327
 
        # resolution tests
328
 
        tree.case_sensitive = False
329
 
        transform = TreeTransform(tree)
330
 
        self.addCleanup(transform.finalize)
331
 
        transform.new_file('file', transform.root, 'content')
332
 
        transform.new_file('FiLe', transform.root, 'content')
333
 
        resolve_conflicts(transform,
334
 
                          pass_func=lambda t, c: resolve_checkout(t, c, []))
335
 
        transform.apply()
336
 
        self.failUnlessExists('tree/file')
337
 
        self.failUnlessExists('tree/FiLe.moved')
338
 
 
339
 
    def test_apply_case_conflict(self):
340
 
        """Ensure that a transform with case conflicts can always be applied"""
341
 
        tree = self.make_branch_and_tree('tree')
342
 
        transform = TreeTransform(tree)
343
 
        self.addCleanup(transform.finalize)
344
 
        transform.new_file('file', transform.root, 'content')
345
 
        transform.new_file('FiLe', transform.root, 'content')
346
 
        dir = transform.new_directory('dir', transform.root)
347
 
        transform.new_file('dirfile', dir, 'content')
348
 
        transform.new_file('dirFiLe', dir, 'content')
349
 
        resolve_conflicts(transform)
350
 
        transform.apply()
351
 
        self.failUnlessExists('tree/file')
352
 
        if not os.path.exists('tree/FiLe.moved'):
353
 
            self.failUnlessExists('tree/FiLe')
354
 
        self.failUnlessExists('tree/dir/dirfile')
355
 
        if not os.path.exists('tree/dir/dirFiLe.moved'):
356
 
            self.failUnlessExists('tree/dir/dirFiLe')
357
 
 
358
 
    def test_case_insensitive_limbo(self):
359
 
        tree = self.make_branch_and_tree('tree')
360
 
        # Don't try this at home, kids!
361
 
        # Force the tree to report that it is case insensitive
362
 
        tree.case_sensitive = False
363
 
        transform = TreeTransform(tree)
364
 
        self.addCleanup(transform.finalize)
365
 
        dir = transform.new_directory('dir', transform.root)
366
 
        first = transform.new_file('file', dir, 'content')
367
 
        second = transform.new_file('FiLe', dir, 'content')
368
 
        self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
369
 
        self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
370
 
 
371
207
    def test_add_del(self):
372
208
        start, root = self.get_transform()
373
209
        start.new_directory('a', root, 'a')
525
361
        replace.apply()
526
362
 
527
363
    def test_symlinks(self):
528
 
        self.requireFeature(SymlinkFeature)
 
364
        if not has_symlinks():
 
365
            raise TestSkipped('Symlinks are not supported on this platform')
529
366
        transform,root = self.get_transform()
530
367
        oz_id = transform.new_directory('oz', root, 'oz-id')
531
368
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
545
382
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
546
383
                         'wizard-target')
547
384
 
548
 
    def test_unable_create_symlink(self):
549
 
        def tt_helper():
550
 
            wt = self.make_branch_and_tree('.')
551
 
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
552
 
            try:
553
 
                tt.new_symlink('foo', tt.root, 'bar')
554
 
                tt.apply()
555
 
            finally:
556
 
                wt.unlock()
557
 
        os_symlink = getattr(os, 'symlink', None)
558
 
        os.symlink = None
559
 
        try:
560
 
            err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
561
 
            self.assertEquals(
562
 
                "Unable to create symlink 'foo' on this platform",
563
 
                str(err))
564
 
        finally:
565
 
            if os_symlink:
566
 
                os.symlink = os_symlink
567
385
 
568
386
    def get_conflicted(self):
569
387
        create,root = self.get_transform()
654
472
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
655
473
                                         ' oz/emeraldcity.  Cancelled move.')
656
474
 
657
 
    def prepare_wrong_parent_kind(self):
658
 
        tt, root = self.get_transform()
659
 
        tt.new_file('parent', root, 'contents', 'parent-id')
660
 
        tt.apply()
661
 
        tt, root = self.get_transform()
662
 
        parent_id = tt.trans_id_file_id('parent-id')
663
 
        tt.new_file('child,', parent_id, 'contents2', 'file-id')
664
 
        return tt
665
 
 
666
 
    def test_find_conflicts_wrong_parent_kind(self):
667
 
        tt = self.prepare_wrong_parent_kind()
668
 
        tt.find_conflicts()
669
 
 
670
 
    def test_resolve_conflicts_wrong_existing_parent_kind(self):
671
 
        tt = self.prepare_wrong_parent_kind()
672
 
        raw_conflicts = resolve_conflicts(tt)
673
 
        self.assertEqual(set([('non-directory parent', 'Created directory',
674
 
                         'new-3')]), raw_conflicts)
675
 
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
676
 
        self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
677
 
        'parent-id')], cooked_conflicts)
678
 
        tt.apply()
679
 
        self.assertEqual(None, self.wt.path2id('parent'))
680
 
        self.assertEqual('parent-id', self.wt.path2id('parent.new'))
681
 
 
682
 
    def test_resolve_conflicts_wrong_new_parent_kind(self):
683
 
        tt, root = self.get_transform()
684
 
        parent_id = tt.new_directory('parent', root, 'parent-id')
685
 
        tt.new_file('child,', parent_id, 'contents2', 'file-id')
686
 
        tt.apply()
687
 
        tt, root = self.get_transform()
688
 
        parent_id = tt.trans_id_file_id('parent-id')
689
 
        tt.delete_contents(parent_id)
690
 
        tt.create_file('contents', parent_id)
691
 
        raw_conflicts = resolve_conflicts(tt)
692
 
        self.assertEqual(set([('non-directory parent', 'Created directory',
693
 
                         'new-3')]), raw_conflicts)
694
 
        tt.apply()
695
 
        self.assertEqual(None, self.wt.path2id('parent'))
696
 
        self.assertEqual('parent-id', self.wt.path2id('parent.new'))
697
 
 
698
 
    def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
699
 
        tt, root = self.get_transform()
700
 
        parent_id = tt.new_directory('parent', root)
701
 
        tt.new_file('child,', parent_id, 'contents2')
702
 
        tt.apply()
703
 
        tt, root = self.get_transform()
704
 
        parent_id = tt.trans_id_tree_path('parent')
705
 
        tt.delete_contents(parent_id)
706
 
        tt.create_file('contents', parent_id)
707
 
        resolve_conflicts(tt)
708
 
        tt.apply()
709
 
        self.assertIs(None, self.wt.path2id('parent'))
710
 
        self.assertIs(None, self.wt.path2id('parent.new'))
711
 
 
712
475
    def test_moving_versioned_directories(self):
713
476
        create, root = self.get_transform()
714
477
        kansas = create.new_directory('kansas', root, 'kansas-id')
753
516
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
754
517
        create.new_file('uvfile', root, 'othertext')
755
518
        create.apply()
756
 
        result = self.applyDeprecated(symbol_versioning.zero_fifteen,
757
 
            find_interesting, wt, wt, ['vfile'])
758
 
        self.assertEqual(result, set(['myfile-id']))
 
519
        self.assertEqual(find_interesting(wt, wt, ['vfile']),
 
520
                         set(['myfile-id']))
 
521
        self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
 
522
                          ['uvfile'])
759
523
 
760
524
    def test_set_executability_order(self):
761
525
        """Ensure that executability behaves the same, no matter what order.
768
532
        """
769
533
        transform, root = self.get_transform()
770
534
        wt = transform._tree
771
 
        wt.lock_read()
772
 
        self.addCleanup(wt.unlock)
773
535
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
774
536
                           True)
775
 
        sac = transform.new_file('set_after_creation', root,
776
 
                                 'Set after creation', 'sac')
 
537
        sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
777
538
        transform.set_executability(True, sac)
778
 
        uws = transform.new_file('unset_without_set', root, 'Unset badly',
779
 
                                 'uws')
 
539
        uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
780
540
        self.assertRaises(KeyError, transform.set_executability, None, uws)
781
541
        transform.apply()
782
542
        self.assertTrue(wt.is_executable('soc'))
789
549
        transform, root = self.get_transform()
790
550
        transform.new_file('file1', root, 'contents', 'file1-id', True)
791
551
        transform.apply()
792
 
        self.wt.lock_write()
793
 
        self.addCleanup(self.wt.unlock)
794
552
        self.assertTrue(self.wt.is_executable('file1-id'))
795
553
        transform, root = self.get_transform()
796
554
        file1_id = transform.trans_id_tree_file_id('file1-id')
829
587
        bar1_abspath = self.wt.abspath('bar')
830
588
        self.assertEqual([bar1_abspath], stat_paths)
831
589
 
832
 
    def test_iter_changes(self):
833
 
        self.wt.set_root_id('eert_toor')
834
 
        transform, root = self.get_transform()
835
 
        transform.new_file('old', root, 'blah', 'id-1', True)
836
 
        transform.apply()
837
 
        transform, root = self.get_transform()
838
 
        try:
839
 
            self.assertEqual([], list(transform.iter_changes()))
840
 
            old = transform.trans_id_tree_file_id('id-1')
841
 
            transform.unversion_file(old)
842
 
            self.assertEqual([('id-1', ('old', None), False, (True, False),
843
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
844
 
                (True, True))], list(transform.iter_changes()))
845
 
            transform.new_directory('new', root, 'id-1')
846
 
            self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
847
 
                ('eert_toor', 'eert_toor'), ('old', 'new'),
848
 
                ('file', 'directory'),
849
 
                (True, False))], list(transform.iter_changes()))
850
 
        finally:
851
 
            transform.finalize()
852
 
 
853
 
    def test_iter_changes_new(self):
854
 
        self.wt.set_root_id('eert_toor')
855
 
        transform, root = self.get_transform()
856
 
        transform.new_file('old', root, 'blah')
857
 
        transform.apply()
858
 
        transform, root = self.get_transform()
859
 
        try:
860
 
            old = transform.trans_id_tree_path('old')
861
 
            transform.version_file('id-1', old)
862
 
            self.assertEqual([('id-1', (None, 'old'), False, (False, True),
863
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
864
 
                (False, False))], list(transform.iter_changes()))
865
 
        finally:
866
 
            transform.finalize()
867
 
 
868
 
    def test_iter_changes_modifications(self):
869
 
        self.wt.set_root_id('eert_toor')
870
 
        transform, root = self.get_transform()
871
 
        transform.new_file('old', root, 'blah', 'id-1')
872
 
        transform.new_file('new', root, 'blah')
873
 
        transform.new_directory('subdir', root, 'subdir-id')
874
 
        transform.apply()
875
 
        transform, root = self.get_transform()
876
 
        try:
877
 
            old = transform.trans_id_tree_path('old')
878
 
            subdir = transform.trans_id_tree_file_id('subdir-id')
879
 
            new = transform.trans_id_tree_path('new')
880
 
            self.assertEqual([], list(transform.iter_changes()))
881
 
 
882
 
            #content deletion
883
 
            transform.delete_contents(old)
884
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
885
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
886
 
                (False, False))], list(transform.iter_changes()))
887
 
 
888
 
            #content change
889
 
            transform.create_file('blah', old)
890
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
891
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
892
 
                (False, False))], list(transform.iter_changes()))
893
 
            transform.cancel_deletion(old)
894
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
895
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
896
 
                (False, False))], list(transform.iter_changes()))
897
 
            transform.cancel_creation(old)
898
 
 
899
 
            # move file_id to a different file
900
 
            self.assertEqual([], list(transform.iter_changes()))
901
 
            transform.unversion_file(old)
902
 
            transform.version_file('id-1', new)
903
 
            transform.adjust_path('old', root, new)
904
 
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
905
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
906
 
                (False, False))], list(transform.iter_changes()))
907
 
            transform.cancel_versioning(new)
908
 
            transform._removed_id = set()
909
 
 
910
 
            #execute bit
911
 
            self.assertEqual([], list(transform.iter_changes()))
912
 
            transform.set_executability(True, old)
913
 
            self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
914
 
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
915
 
                (False, True))], list(transform.iter_changes()))
916
 
            transform.set_executability(None, old)
917
 
 
918
 
            # filename
919
 
            self.assertEqual([], list(transform.iter_changes()))
920
 
            transform.adjust_path('new', root, old)
921
 
            transform._new_parent = {}
922
 
            self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
923
 
                ('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
924
 
                (False, False))], list(transform.iter_changes()))
925
 
            transform._new_name = {}
926
 
 
927
 
            # parent directory
928
 
            self.assertEqual([], list(transform.iter_changes()))
929
 
            transform.adjust_path('new', subdir, old)
930
 
            transform._new_name = {}
931
 
            self.assertEqual([('id-1', ('old', 'subdir/old'), False,
932
 
                (True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
933
 
                ('file', 'file'), (False, False))],
934
 
                list(transform.iter_changes()))
935
 
            transform._new_path = {}
936
 
 
937
 
        finally:
938
 
            transform.finalize()
939
 
 
940
 
    def test_iter_changes_modified_bleed(self):
941
 
        self.wt.set_root_id('eert_toor')
942
 
        """Modified flag should not bleed from one change to another"""
943
 
        # unfortunately, we have no guarantee that file1 (which is modified)
944
 
        # will be applied before file2.  And if it's applied after file2, it
945
 
        # obviously can't bleed into file2's change output.  But for now, it
946
 
        # works.
947
 
        transform, root = self.get_transform()
948
 
        transform.new_file('file1', root, 'blah', 'id-1')
949
 
        transform.new_file('file2', root, 'blah', 'id-2')
950
 
        transform.apply()
951
 
        transform, root = self.get_transform()
952
 
        try:
953
 
            transform.delete_contents(transform.trans_id_file_id('id-1'))
954
 
            transform.set_executability(True,
955
 
            transform.trans_id_file_id('id-2'))
956
 
            self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
957
 
                ('eert_toor', 'eert_toor'), ('file1', u'file1'),
958
 
                ('file', None), (False, False)),
959
 
                ('id-2', (u'file2', u'file2'), False, (True, True),
960
 
                ('eert_toor', 'eert_toor'), ('file2', u'file2'),
961
 
                ('file', 'file'), (False, True))],
962
 
                list(transform.iter_changes()))
963
 
        finally:
964
 
            transform.finalize()
965
 
 
966
 
    def test_iter_changes_move_missing(self):
967
 
        """Test moving ids with no files around"""
968
 
        self.wt.set_root_id('toor_eert')
969
 
        # Need two steps because versioning a non-existant file is a conflict.
970
 
        transform, root = self.get_transform()
971
 
        transform.new_directory('floater', root, 'floater-id')
972
 
        transform.apply()
973
 
        transform, root = self.get_transform()
974
 
        transform.delete_contents(transform.trans_id_tree_path('floater'))
975
 
        transform.apply()
976
 
        transform, root = self.get_transform()
977
 
        floater = transform.trans_id_tree_path('floater')
978
 
        try:
979
 
            transform.adjust_path('flitter', root, floater)
980
 
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
981
 
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
982
 
            (None, None), (False, False))], list(transform.iter_changes()))
983
 
        finally:
984
 
            transform.finalize()
985
 
 
986
 
    def test_iter_changes_pointless(self):
987
 
        """Ensure that no-ops are not treated as modifications"""
988
 
        self.wt.set_root_id('eert_toor')
989
 
        transform, root = self.get_transform()
990
 
        transform.new_file('old', root, 'blah', 'id-1')
991
 
        transform.new_directory('subdir', root, 'subdir-id')
992
 
        transform.apply()
993
 
        transform, root = self.get_transform()
994
 
        try:
995
 
            old = transform.trans_id_tree_path('old')
996
 
            subdir = transform.trans_id_tree_file_id('subdir-id')
997
 
            self.assertEqual([], list(transform.iter_changes()))
998
 
            transform.delete_contents(subdir)
999
 
            transform.create_directory(subdir)
1000
 
            transform.set_executability(False, old)
1001
 
            transform.unversion_file(old)
1002
 
            transform.version_file('id-1', old)
1003
 
            transform.adjust_path('old', root, old)
1004
 
            self.assertEqual([], list(transform.iter_changes()))
1005
 
        finally:
1006
 
            transform.finalize()
1007
 
 
1008
 
    def test_rename_count(self):
1009
 
        transform, root = self.get_transform()
1010
 
        transform.new_file('name1', root, 'contents')
1011
 
        self.assertEqual(transform.rename_count, 0)
1012
 
        transform.apply()
1013
 
        self.assertEqual(transform.rename_count, 1)
1014
 
        transform2, root = self.get_transform()
1015
 
        transform2.adjust_path('name2', root,
1016
 
                               transform2.trans_id_tree_path('name1'))
1017
 
        self.assertEqual(transform2.rename_count, 0)
1018
 
        transform2.apply()
1019
 
        self.assertEqual(transform2.rename_count, 2)
1020
 
 
1021
 
    def test_change_parent(self):
1022
 
        """Ensure that after we change a parent, the results are still right.
1023
 
 
1024
 
        Renames and parent changes on pending transforms can happen as part
1025
 
        of conflict resolution, and are explicitly permitted by the
1026
 
        TreeTransform API.
1027
 
 
1028
 
        This test ensures they work correctly with the rename-avoidance
1029
 
        optimization.
1030
 
        """
1031
 
        transform, root = self.get_transform()
1032
 
        parent1 = transform.new_directory('parent1', root)
1033
 
        child1 = transform.new_file('child1', parent1, 'contents')
1034
 
        parent2 = transform.new_directory('parent2', root)
1035
 
        transform.adjust_path('child1', parent2, child1)
1036
 
        transform.apply()
1037
 
        self.failIfExists(self.wt.abspath('parent1/child1'))
1038
 
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
1039
 
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
1040
 
        # no rename for child1 (counting only renames during apply)
1041
 
        self.failUnlessEqual(2, transform.rename_count)
1042
 
 
1043
 
    def test_cancel_parent(self):
1044
 
        """Cancelling a parent doesn't cause deletion of a non-empty directory
1045
 
 
1046
 
        This is like the test_change_parent, except that we cancel the parent
1047
 
        before adjusting the path.  The transform must detect that the
1048
 
        directory is non-empty, and move children to safe locations.
1049
 
        """
1050
 
        transform, root = self.get_transform()
1051
 
        parent1 = transform.new_directory('parent1', root)
1052
 
        child1 = transform.new_file('child1', parent1, 'contents')
1053
 
        child2 = transform.new_file('child2', parent1, 'contents')
1054
 
        try:
1055
 
            transform.cancel_creation(parent1)
1056
 
        except OSError:
1057
 
            self.fail('Failed to move child1 before deleting parent1')
1058
 
        transform.cancel_creation(child2)
1059
 
        transform.create_directory(parent1)
1060
 
        try:
1061
 
            transform.cancel_creation(parent1)
1062
 
        # If the transform incorrectly believes that child2 is still in
1063
 
        # parent1's limbo directory, it will try to rename it and fail
1064
 
        # because was already moved by the first cancel_creation.
1065
 
        except OSError:
1066
 
            self.fail('Transform still thinks child2 is a child of parent1')
1067
 
        parent2 = transform.new_directory('parent2', root)
1068
 
        transform.adjust_path('child1', parent2, child1)
1069
 
        transform.apply()
1070
 
        self.failIfExists(self.wt.abspath('parent1'))
1071
 
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
1072
 
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
1073
 
        self.failUnlessEqual(2, transform.rename_count)
1074
 
 
1075
 
    def test_adjust_and_cancel(self):
1076
 
        """Make sure adjust_path keeps track of limbo children properly"""
1077
 
        transform, root = self.get_transform()
1078
 
        parent1 = transform.new_directory('parent1', root)
1079
 
        child1 = transform.new_file('child1', parent1, 'contents')
1080
 
        parent2 = transform.new_directory('parent2', root)
1081
 
        transform.adjust_path('child1', parent2, child1)
1082
 
        transform.cancel_creation(child1)
1083
 
        try:
1084
 
            transform.cancel_creation(parent1)
1085
 
        # if the transform thinks child1 is still in parent1's limbo
1086
 
        # directory, it will attempt to move it and fail.
1087
 
        except OSError:
1088
 
            self.fail('Transform still thinks child1 is a child of parent1')
1089
 
        transform.finalize()
1090
 
 
1091
 
    def test_noname_contents(self):
1092
 
        """TreeTransform should permit deferring naming files."""
1093
 
        transform, root = self.get_transform()
1094
 
        parent = transform.trans_id_file_id('parent-id')
1095
 
        try:
1096
 
            transform.create_directory(parent)
1097
 
        except KeyError:
1098
 
            self.fail("Can't handle contents with no name")
1099
 
        transform.finalize()
1100
 
 
1101
 
    def test_noname_contents_nested(self):
1102
 
        """TreeTransform should permit deferring naming files."""
1103
 
        transform, root = self.get_transform()
1104
 
        parent = transform.trans_id_file_id('parent-id')
1105
 
        try:
1106
 
            transform.create_directory(parent)
1107
 
        except KeyError:
1108
 
            self.fail("Can't handle contents with no name")
1109
 
        child = transform.new_directory('child', parent)
1110
 
        transform.adjust_path('parent', root, parent)
1111
 
        transform.apply()
1112
 
        self.failUnlessExists(self.wt.abspath('parent/child'))
1113
 
        self.assertEqual(1, transform.rename_count)
1114
 
 
1115
 
    def test_reuse_name(self):
1116
 
        """Avoid reusing the same limbo name for different files"""
1117
 
        transform, root = self.get_transform()
1118
 
        parent = transform.new_directory('parent', root)
1119
 
        child1 = transform.new_directory('child', parent)
1120
 
        try:
1121
 
            child2 = transform.new_directory('child', parent)
1122
 
        except OSError:
1123
 
            self.fail('Tranform tried to use the same limbo name twice')
1124
 
        transform.adjust_path('child2', parent, child2)
1125
 
        transform.apply()
1126
 
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
1127
 
        # child2 is put into top-level limbo because child1 has already
1128
 
        # claimed the direct limbo path when child2 is created.  There is no
1129
 
        # advantage in renaming files once they're in top-level limbo, except
1130
 
        # as part of apply.
1131
 
        self.assertEqual(2, transform.rename_count)
1132
 
 
1133
 
    def test_reuse_when_first_moved(self):
1134
 
        """Don't avoid direct paths when it is safe to use them"""
1135
 
        transform, root = self.get_transform()
1136
 
        parent = transform.new_directory('parent', root)
1137
 
        child1 = transform.new_directory('child', parent)
1138
 
        transform.adjust_path('child1', parent, child1)
1139
 
        child2 = transform.new_directory('child', parent)
1140
 
        transform.apply()
1141
 
        # limbo/new-1 => parent
1142
 
        self.assertEqual(1, transform.rename_count)
1143
 
 
1144
 
    def test_reuse_after_cancel(self):
1145
 
        """Don't avoid direct paths when it is safe to use them"""
1146
 
        transform, root = self.get_transform()
1147
 
        parent2 = transform.new_directory('parent2', root)
1148
 
        child1 = transform.new_directory('child1', parent2)
1149
 
        transform.cancel_creation(parent2)
1150
 
        transform.create_directory(parent2)
1151
 
        child2 = transform.new_directory('child1', parent2)
1152
 
        transform.adjust_path('child2', parent2, child1)
1153
 
        transform.apply()
1154
 
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
1155
 
        self.assertEqual(2, transform.rename_count)
1156
 
 
1157
 
    def test_finalize_order(self):
1158
 
        """Finalize must be done in child-to-parent order"""
1159
 
        transform, root = self.get_transform()
1160
 
        parent = transform.new_directory('parent', root)
1161
 
        child = transform.new_directory('child', parent)
1162
 
        try:
1163
 
            transform.finalize()
1164
 
        except OSError:
1165
 
            self.fail('Tried to remove parent before child1')
1166
 
 
1167
 
    def test_cancel_with_cancelled_child_should_succeed(self):
1168
 
        transform, root = self.get_transform()
1169
 
        parent = transform.new_directory('parent', root)
1170
 
        child = transform.new_directory('child', parent)
1171
 
        transform.cancel_creation(child)
1172
 
        transform.cancel_creation(parent)
1173
 
        transform.finalize()
1174
 
 
1175
 
    def test_change_entry(self):
1176
 
        txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
1177
 
        self.callDeprecated([txt], change_entry, None, None, None, None, None,
1178
 
            None, None, None)
1179
 
 
1180
 
    def test_case_insensitive_clash(self):
1181
 
        self.requireFeature(CaseInsensitiveFilesystemFeature)
1182
 
        def tt_helper():
1183
 
            wt = self.make_branch_and_tree('.')
1184
 
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1185
 
            try:
1186
 
                tt.new_file('foo', tt.root, 'bar')
1187
 
                tt.new_file('Foo', tt.root, 'spam')
1188
 
                # Lie to tt that we've already resolved all conflicts.
1189
 
                tt.apply(no_conflicts=True)
1190
 
            except:
1191
 
                wt.unlock()
1192
 
                raise
1193
 
        err = self.assertRaises(errors.FileExists, tt_helper)
1194
 
        self.assertContainsRe(str(err),
1195
 
            "^File exists: .+/foo")
1196
 
 
1197
 
    def test_two_directories_clash(self):
1198
 
        def tt_helper():
1199
 
            wt = self.make_branch_and_tree('.')
1200
 
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1201
 
            try:
1202
 
                foo_1 = tt.new_directory('foo', tt.root)
1203
 
                tt.new_directory('bar', foo_1)
1204
 
                foo_2 = tt.new_directory('foo', tt.root)
1205
 
                tt.new_directory('baz', foo_2)
1206
 
                # Lie to tt that we've already resolved all conflicts.
1207
 
                tt.apply(no_conflicts=True)
1208
 
            except:
1209
 
                wt.unlock()
1210
 
                raise
1211
 
        err = self.assertRaises(errors.FileExists, tt_helper)
1212
 
        self.assertContainsRe(str(err),
1213
 
            "^File exists: .+/foo")
1214
 
 
1215
 
    def test_two_directories_clash_finalize(self):
1216
 
        def tt_helper():
1217
 
            wt = self.make_branch_and_tree('.')
1218
 
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
1219
 
            try:
1220
 
                foo_1 = tt.new_directory('foo', tt.root)
1221
 
                tt.new_directory('bar', foo_1)
1222
 
                foo_2 = tt.new_directory('foo', tt.root)
1223
 
                tt.new_directory('baz', foo_2)
1224
 
                # Lie to tt that we've already resolved all conflicts.
1225
 
                tt.apply(no_conflicts=True)
1226
 
            except:
1227
 
                tt.finalize()
1228
 
                raise
1229
 
        err = self.assertRaises(errors.FileExists, tt_helper)
1230
 
        self.assertContainsRe(str(err),
1231
 
            "^File exists: .+/foo")
1232
 
 
1233
590
 
1234
591
class TransformGroup(object):
1235
 
 
1236
592
    def __init__(self, dirname, root_id):
1237
593
        self.name = dirname
1238
594
        os.mkdir(dirname)
1242
598
        self.tt = TreeTransform(self.wt)
1243
599
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
1244
600
 
1245
 
 
1246
601
def conflict_text(tree, merge):
1247
602
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
1248
603
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
1249
604
 
1250
605
 
1251
606
class TestTransformMerge(TestCaseInTempDir):
1252
 
 
1253
607
    def test_text_merge(self):
1254
608
        root_id = generate_ids.gen_root_id()
1255
609
        base = TransformGroup("base", root_id)
1285
639
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
1286
640
        this.tt.apply()
1287
641
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
1288
 
 
1289
642
        # textual merge
1290
643
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
1291
644
        # three-way text conflict
1326
679
        self.assertSubset(merge_modified, modified)
1327
680
        self.assertEqual(len(merge_modified), len(modified))
1328
681
        this.wt.remove('b')
1329
 
        this.wt.revert()
 
682
        this.wt.revert([])
1330
683
 
1331
684
    def test_file_merge(self):
1332
 
        self.requireFeature(SymlinkFeature)
 
685
        if not has_symlinks():
 
686
            raise TestSkipped('Symlinks are not supported on this platform')
1333
687
        root_id = generate_ids.gen_root_id()
1334
688
        base = TransformGroup("BASE", root_id)
1335
689
        this = TransformGroup("THIS", root_id)
1437
791
 
1438
792
class TestBuildTree(tests.TestCaseWithTransport):
1439
793
 
1440
 
    def test_build_tree_with_symlinks(self):
1441
 
        self.requireFeature(SymlinkFeature)
 
794
    def test_build_tree(self):
 
795
        if not has_symlinks():
 
796
            raise TestSkipped('Test requires symlink support')
1442
797
        os.mkdir('a')
1443
798
        a = BzrDir.create_standalone_workingtree('a')
1444
799
        os.mkdir('a/foo')
1447
802
        a.add(['foo', 'foo/bar', 'foo/baz'])
1448
803
        a.commit('initial commit')
1449
804
        b = BzrDir.create_standalone_workingtree('b')
1450
 
        basis = a.basis_tree()
1451
 
        basis.lock_read()
1452
 
        self.addCleanup(basis.unlock)
1453
 
        build_tree(basis, b)
 
805
        build_tree(a.basis_tree(), b)
1454
806
        self.assertIs(os.path.isdir('b/foo'), True)
1455
807
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
1456
808
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
1457
809
 
1458
 
    def test_build_with_references(self):
1459
 
        tree = self.make_branch_and_tree('source',
1460
 
            format='dirstate-with-subtree')
1461
 
        subtree = self.make_branch_and_tree('source/subtree',
1462
 
            format='dirstate-with-subtree')
1463
 
        tree.add_reference(subtree)
1464
 
        tree.commit('a revision')
1465
 
        tree.branch.create_checkout('target')
1466
 
        self.failUnlessExists('target')
1467
 
        self.failUnlessExists('target/subtree')
1468
 
 
1469
810
    def test_file_conflict_handling(self):
1470
811
        """Ensure that when building trees, conflict handling is done"""
1471
812
        source = self.make_branch_and_tree('source')
1492
833
 
1493
834
    def test_symlink_conflict_handling(self):
1494
835
        """Ensure that when building trees, conflict handling is done"""
1495
 
        self.requireFeature(SymlinkFeature)
 
836
        if not has_symlinks():
 
837
            raise TestSkipped('Test requires symlink support')
1496
838
        source = self.make_branch_and_tree('source')
1497
839
        os.symlink('foo', 'source/symlink')
1498
840
        source.add('symlink', 'new-symlink')
1572
914
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
1573
915
            build_tree, source.basis_tree(), target)
1574
916
 
1575
 
    def test_build_tree_rename_count(self):
1576
 
        source = self.make_branch_and_tree('source')
1577
 
        self.build_tree(['source/file1', 'source/dir1/'])
1578
 
        source.add(['file1', 'dir1'])
1579
 
        source.commit('add1')
1580
 
        target1 = self.make_branch_and_tree('target1')
1581
 
        transform_result = build_tree(source.basis_tree(), target1)
1582
 
        self.assertEqual(2, transform_result.rename_count)
1583
 
 
1584
 
        self.build_tree(['source/dir1/file2'])
1585
 
        source.add(['dir1/file2'])
1586
 
        source.commit('add3')
1587
 
        target2 = self.make_branch_and_tree('target2')
1588
 
        transform_result = build_tree(source.basis_tree(), target2)
1589
 
        # children of non-root directories should not be renamed
1590
 
        self.assertEqual(2, transform_result.rename_count)
1591
 
 
1592
 
    def create_ab_tree(self):
1593
 
        """Create a committed test tree with two files"""
1594
 
        source = self.make_branch_and_tree('source')
1595
 
        self.build_tree_contents([('source/file1', 'A')])
1596
 
        self.build_tree_contents([('source/file2', 'B')])
1597
 
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1598
 
        source.commit('commit files')
1599
 
        source.lock_write()
1600
 
        self.addCleanup(source.unlock)
1601
 
        return source
1602
 
 
1603
 
    def test_build_tree_accelerator_tree(self):
1604
 
        source = self.create_ab_tree()
1605
 
        self.build_tree_contents([('source/file2', 'C')])
1606
 
        calls = []
1607
 
        real_source_get_file = source.get_file
1608
 
        def get_file(file_id, path=None):
1609
 
            calls.append(file_id)
1610
 
            return real_source_get_file(file_id, path)
1611
 
        source.get_file = get_file
1612
 
        target = self.make_branch_and_tree('target')
1613
 
        revision_tree = source.basis_tree()
1614
 
        revision_tree.lock_read()
1615
 
        self.addCleanup(revision_tree.unlock)
1616
 
        build_tree(revision_tree, target, source)
1617
 
        self.assertEqual(['file1-id'], calls)
1618
 
        target.lock_read()
1619
 
        self.addCleanup(target.unlock)
1620
 
        self.assertEqual([], list(target.iter_changes(revision_tree)))
1621
 
 
1622
 
    def test_build_tree_accelerator_tree_missing_file(self):
1623
 
        source = self.create_ab_tree()
1624
 
        os.unlink('source/file1')
1625
 
        source.remove(['file2'])
1626
 
        target = self.make_branch_and_tree('target')
1627
 
        revision_tree = source.basis_tree()
1628
 
        revision_tree.lock_read()
1629
 
        self.addCleanup(revision_tree.unlock)
1630
 
        build_tree(revision_tree, target, source)
1631
 
        target.lock_read()
1632
 
        self.addCleanup(target.unlock)
1633
 
        self.assertEqual([], list(target.iter_changes(revision_tree)))
1634
 
 
1635
 
    def test_build_tree_accelerator_wrong_kind(self):
1636
 
        self.requireFeature(SymlinkFeature)
1637
 
        source = self.make_branch_and_tree('source')
1638
 
        self.build_tree_contents([('source/file1', '')])
1639
 
        self.build_tree_contents([('source/file2', '')])
1640
 
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
1641
 
        source.commit('commit files')
1642
 
        os.unlink('source/file2')
1643
 
        self.build_tree_contents([('source/file2/', 'C')])
1644
 
        os.unlink('source/file1')
1645
 
        os.symlink('file2', 'source/file1')
1646
 
        calls = []
1647
 
        real_source_get_file = source.get_file
1648
 
        def get_file(file_id, path=None):
1649
 
            calls.append(file_id)
1650
 
            return real_source_get_file(file_id, path)
1651
 
        source.get_file = get_file
1652
 
        target = self.make_branch_and_tree('target')
1653
 
        revision_tree = source.basis_tree()
1654
 
        revision_tree.lock_read()
1655
 
        self.addCleanup(revision_tree.unlock)
1656
 
        build_tree(revision_tree, target, source)
1657
 
        self.assertEqual([], calls)
1658
 
        target.lock_read()
1659
 
        self.addCleanup(target.unlock)
1660
 
        self.assertEqual([], list(target.iter_changes(revision_tree)))
1661
 
 
1662
 
    def test_build_tree_hardlink(self):
1663
 
        self.requireFeature(HardlinkFeature)
1664
 
        source = self.create_ab_tree()
1665
 
        target = self.make_branch_and_tree('target')
1666
 
        revision_tree = source.basis_tree()
1667
 
        revision_tree.lock_read()
1668
 
        self.addCleanup(revision_tree.unlock)
1669
 
        build_tree(revision_tree, target, source, hardlink=True)
1670
 
        target.lock_read()
1671
 
        self.addCleanup(target.unlock)
1672
 
        self.assertEqual([], list(target.iter_changes(revision_tree)))
1673
 
        source_stat = os.stat('source/file1')
1674
 
        target_stat = os.stat('target/file1')
1675
 
        self.assertEqual(source_stat, target_stat)
1676
 
 
1677
 
        # Explicitly disallowing hardlinks should prevent them.
1678
 
        target2 = self.make_branch_and_tree('target2')
1679
 
        build_tree(revision_tree, target2, source, hardlink=False)
1680
 
        target2.lock_read()
1681
 
        self.addCleanup(target2.unlock)
1682
 
        self.assertEqual([], list(target2.iter_changes(revision_tree)))
1683
 
        source_stat = os.stat('source/file1')
1684
 
        target2_stat = os.stat('target2/file1')
1685
 
        self.assertNotEqual(source_stat, target2_stat)
1686
 
 
1687
 
    def test_build_tree_accelerator_tree_moved(self):
1688
 
        source = self.make_branch_and_tree('source')
1689
 
        self.build_tree_contents([('source/file1', 'A')])
1690
 
        source.add(['file1'], ['file1-id'])
1691
 
        source.commit('commit files')
1692
 
        source.rename_one('file1', 'file2')
1693
 
        source.lock_read()
1694
 
        self.addCleanup(source.unlock)
1695
 
        target = self.make_branch_and_tree('target')
1696
 
        revision_tree = source.basis_tree()
1697
 
        revision_tree.lock_read()
1698
 
        self.addCleanup(revision_tree.unlock)
1699
 
        build_tree(revision_tree, target, source)
1700
 
        target.lock_read()
1701
 
        self.addCleanup(target.unlock)
1702
 
        self.assertEqual([], list(target.iter_changes(revision_tree)))
1703
 
 
1704
 
    def test_build_tree_hardlinks_preserve_execute(self):
1705
 
        self.requireFeature(HardlinkFeature)
1706
 
        source = self.create_ab_tree()
1707
 
        tt = TreeTransform(source)
1708
 
        trans_id = tt.trans_id_tree_file_id('file1-id')
1709
 
        tt.set_executability(True, trans_id)
1710
 
        tt.apply()
1711
 
        self.assertTrue(source.is_executable('file1-id'))
1712
 
        target = self.make_branch_and_tree('target')
1713
 
        revision_tree = source.basis_tree()
1714
 
        revision_tree.lock_read()
1715
 
        self.addCleanup(revision_tree.unlock)
1716
 
        build_tree(revision_tree, target, source, hardlink=True)
1717
 
        target.lock_read()
1718
 
        self.addCleanup(target.unlock)
1719
 
        self.assertEqual([], list(target.iter_changes(revision_tree)))
1720
 
        self.assertTrue(source.is_executable('file1-id'))
1721
 
 
1722
917
 
1723
918
class MockTransform(object):
1724
919
 
1731
926
                return True
1732
927
        return False
1733
928
 
1734
 
 
1735
929
class MockEntry(object):
1736
930
    def __init__(self):
1737
931
        object.__init__(self)
1738
932
        self.name = "name"
1739
933
 
1740
 
 
1741
934
class TestGetBackupName(TestCase):
1742
935
    def test_get_backup_name(self):
1743
936
        tt = MockTransform()
1751
944
        self.assertEqual(name, 'name.~1~')
1752
945
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
1753
946
        self.assertEqual(name, 'name.~4~')
1754
 
 
1755
 
 
1756
 
class TestFileMover(tests.TestCaseWithTransport):
1757
 
 
1758
 
    def test_file_mover(self):
1759
 
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
1760
 
        mover = _FileMover()
1761
 
        mover.rename('a', 'q')
1762
 
        self.failUnlessExists('q')
1763
 
        self.failIfExists('a')
1764
 
        self.failUnlessExists('q/b')
1765
 
        self.failUnlessExists('c')
1766
 
        self.failUnlessExists('c/d')
1767
 
 
1768
 
    def test_pre_delete_rollback(self):
1769
 
        self.build_tree(['a/'])
1770
 
        mover = _FileMover()
1771
 
        mover.pre_delete('a', 'q')
1772
 
        self.failUnlessExists('q')
1773
 
        self.failIfExists('a')
1774
 
        mover.rollback()
1775
 
        self.failIfExists('q')
1776
 
        self.failUnlessExists('a')
1777
 
 
1778
 
    def test_apply_deletions(self):
1779
 
        self.build_tree(['a/', 'b/'])
1780
 
        mover = _FileMover()
1781
 
        mover.pre_delete('a', 'q')
1782
 
        mover.pre_delete('b', 'r')
1783
 
        self.failUnlessExists('q')
1784
 
        self.failUnlessExists('r')
1785
 
        self.failIfExists('a')
1786
 
        self.failIfExists('b')
1787
 
        mover.apply_deletions()
1788
 
        self.failIfExists('q')
1789
 
        self.failIfExists('r')
1790
 
        self.failIfExists('a')
1791
 
        self.failIfExists('b')
1792
 
 
1793
 
    def test_file_mover_rollback(self):
1794
 
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
1795
 
        mover = _FileMover()
1796
 
        mover.rename('c/d', 'c/f')
1797
 
        mover.rename('c/e', 'c/d')
1798
 
        try:
1799
 
            mover.rename('a', 'c')
1800
 
        except errors.FileExists, e:
1801
 
            mover.rollback()
1802
 
        self.failUnlessExists('a')
1803
 
        self.failUnlessExists('c/d')
1804
 
 
1805
 
 
1806
 
class Bogus(Exception):
1807
 
    pass
1808
 
 
1809
 
 
1810
 
class TestTransformRollback(tests.TestCaseWithTransport):
1811
 
 
1812
 
    class ExceptionFileMover(_FileMover):
1813
 
 
1814
 
        def __init__(self, bad_source=None, bad_target=None):
1815
 
            _FileMover.__init__(self)
1816
 
            self.bad_source = bad_source
1817
 
            self.bad_target = bad_target
1818
 
 
1819
 
        def rename(self, source, target):
1820
 
            if (self.bad_source is not None and
1821
 
                source.endswith(self.bad_source)):
1822
 
                raise Bogus
1823
 
            elif (self.bad_target is not None and
1824
 
                target.endswith(self.bad_target)):
1825
 
                raise Bogus
1826
 
            else:
1827
 
                _FileMover.rename(self, source, target)
1828
 
 
1829
 
    def test_rollback_rename(self):
1830
 
        tree = self.make_branch_and_tree('.')
1831
 
        self.build_tree(['a/', 'a/b'])
1832
 
        tt = TreeTransform(tree)
1833
 
        self.addCleanup(tt.finalize)
1834
 
        a_id = tt.trans_id_tree_path('a')
1835
 
        tt.adjust_path('c', tt.root, a_id)
1836
 
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1837
 
        self.assertRaises(Bogus, tt.apply,
1838
 
                          _mover=self.ExceptionFileMover(bad_source='a'))
1839
 
        self.failUnlessExists('a')
1840
 
        self.failUnlessExists('a/b')
1841
 
        tt.apply()
1842
 
        self.failUnlessExists('c')
1843
 
        self.failUnlessExists('c/d')
1844
 
 
1845
 
    def test_rollback_rename_into_place(self):
1846
 
        tree = self.make_branch_and_tree('.')
1847
 
        self.build_tree(['a/', 'a/b'])
1848
 
        tt = TreeTransform(tree)
1849
 
        self.addCleanup(tt.finalize)
1850
 
        a_id = tt.trans_id_tree_path('a')
1851
 
        tt.adjust_path('c', tt.root, a_id)
1852
 
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
1853
 
        self.assertRaises(Bogus, tt.apply,
1854
 
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
1855
 
        self.failUnlessExists('a')
1856
 
        self.failUnlessExists('a/b')
1857
 
        tt.apply()
1858
 
        self.failUnlessExists('c')
1859
 
        self.failUnlessExists('c/d')
1860
 
 
1861
 
    def test_rollback_deletion(self):
1862
 
        tree = self.make_branch_and_tree('.')
1863
 
        self.build_tree(['a/', 'a/b'])
1864
 
        tt = TreeTransform(tree)
1865
 
        self.addCleanup(tt.finalize)
1866
 
        a_id = tt.trans_id_tree_path('a')
1867
 
        tt.delete_contents(a_id)
1868
 
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
1869
 
        self.assertRaises(Bogus, tt.apply,
1870
 
                          _mover=self.ExceptionFileMover(bad_target='d'))
1871
 
        self.failUnlessExists('a')
1872
 
        self.failUnlessExists('a/b')
1873
 
 
1874
 
    def test_resolve_no_parent(self):
1875
 
        wt = self.make_branch_and_tree('.')
1876
 
        tt = TreeTransform(wt)
1877
 
        self.addCleanup(tt.finalize)
1878
 
        parent = tt.trans_id_file_id('parent-id')
1879
 
        tt.new_file('file', parent, 'Contents')
1880
 
        resolve_conflicts(tt)
1881
 
 
1882
 
 
1883
 
class TestTransformPreview(tests.TestCaseWithTransport):
1884
 
 
1885
 
    def create_tree(self):
1886
 
        tree = self.make_branch_and_tree('.')
1887
 
        self.build_tree_contents([('a', 'content 1')])
1888
 
        tree.add('a', 'a-id')
1889
 
        tree.commit('rev1', rev_id='rev1')
1890
 
        return tree.branch.repository.revision_tree('rev1')
1891
 
 
1892
 
    def get_empty_preview(self):
1893
 
        repository = self.make_repository('repo')
1894
 
        tree = repository.revision_tree(_mod_revision.NULL_REVISION)
1895
 
        preview = TransformPreview(tree)
1896
 
        self.addCleanup(preview.finalize)
1897
 
        return preview
1898
 
 
1899
 
    def test_transform_preview(self):
1900
 
        revision_tree = self.create_tree()
1901
 
        preview = TransformPreview(revision_tree)
1902
 
        self.addCleanup(preview.finalize)
1903
 
 
1904
 
    def test_transform_preview_tree(self):
1905
 
        revision_tree = self.create_tree()
1906
 
        preview = TransformPreview(revision_tree)
1907
 
        self.addCleanup(preview.finalize)
1908
 
        preview.get_preview_tree()
1909
 
 
1910
 
    def test_transform_new_file(self):
1911
 
        revision_tree = self.create_tree()
1912
 
        preview = TransformPreview(revision_tree)
1913
 
        self.addCleanup(preview.finalize)
1914
 
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
1915
 
        preview_tree = preview.get_preview_tree()
1916
 
        self.assertEqual(preview_tree.kind('file2-id'), 'file')
1917
 
        self.assertEqual(
1918
 
            preview_tree.get_file('file2-id').read(), 'content B\n')
1919
 
 
1920
 
    def test_diff_preview_tree(self):
1921
 
        revision_tree = self.create_tree()
1922
 
        preview = TransformPreview(revision_tree)
1923
 
        self.addCleanup(preview.finalize)
1924
 
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
1925
 
        preview_tree = preview.get_preview_tree()
1926
 
        out = StringIO()
1927
 
        show_diff_trees(revision_tree, preview_tree, out)
1928
 
        lines = out.getvalue().splitlines()
1929
 
        self.assertEqual(lines[0], "=== added file 'file2'")
1930
 
        # 3 lines of diff administrivia
1931
 
        self.assertEqual(lines[4], "+content B")
1932
 
 
1933
 
    def test_transform_conflicts(self):
1934
 
        revision_tree = self.create_tree()
1935
 
        preview = TransformPreview(revision_tree)
1936
 
        self.addCleanup(preview.finalize)
1937
 
        preview.new_file('a', preview.root, 'content 2')
1938
 
        resolve_conflicts(preview)
1939
 
        trans_id = preview.trans_id_file_id('a-id')
1940
 
        self.assertEqual('a.moved', preview.final_name(trans_id))
1941
 
 
1942
 
    def get_tree_and_preview_tree(self):
1943
 
        revision_tree = self.create_tree()
1944
 
        preview = TransformPreview(revision_tree)
1945
 
        self.addCleanup(preview.finalize)
1946
 
        a_trans_id = preview.trans_id_file_id('a-id')
1947
 
        preview.delete_contents(a_trans_id)
1948
 
        preview.create_file('b content', a_trans_id)
1949
 
        preview_tree = preview.get_preview_tree()
1950
 
        return revision_tree, preview_tree
1951
 
 
1952
 
    def test_iter_changes(self):
1953
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
1954
 
        root = revision_tree.inventory.root.file_id
1955
 
        self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
1956
 
                          (root, root), ('a', 'a'), ('file', 'file'),
1957
 
                          (False, False))],
1958
 
                          list(preview_tree.iter_changes(revision_tree)))
1959
 
 
1960
 
    def test_wrong_tree_value_error(self):
1961
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
1962
 
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
1963
 
                              preview_tree)
1964
 
        self.assertEqual('from_tree must be transform source tree.', str(e))
1965
 
 
1966
 
    def test_include_unchanged_value_error(self):
1967
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
1968
 
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
1969
 
                              revision_tree, include_unchanged=True)
1970
 
        self.assertEqual('include_unchanged is not supported', str(e))
1971
 
 
1972
 
    def test_specific_files(self):
1973
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
1974
 
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
1975
 
                              revision_tree, specific_files=['pete'])
1976
 
        self.assertEqual('specific_files is not supported', str(e))
1977
 
 
1978
 
    def test_want_unversioned_value_error(self):
1979
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
1980
 
        e = self.assertRaises(ValueError, preview_tree.iter_changes,
1981
 
                              revision_tree, want_unversioned=True)
1982
 
        self.assertEqual('want_unversioned is not supported', str(e))
1983
 
 
1984
 
    def test_ignore_extra_trees_no_specific_files(self):
1985
 
        # extra_trees is harmless without specific_files, so we'll silently
1986
 
        # accept it, even though we won't use it.
1987
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
1988
 
        preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
1989
 
 
1990
 
    def test_ignore_require_versioned_no_specific_files(self):
1991
 
        # require_versioned is meaningless without specific_files.
1992
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
1993
 
        preview_tree.iter_changes(revision_tree, require_versioned=False)
1994
 
 
1995
 
    def test_ignore_pb(self):
1996
 
        # pb could be supported, but TT.iter_changes doesn't support it.
1997
 
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
1998
 
        preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
1999
 
 
2000
 
    def test_kind(self):
2001
 
        revision_tree = self.create_tree()
2002
 
        preview = TransformPreview(revision_tree)
2003
 
        self.addCleanup(preview.finalize)
2004
 
        preview.new_file('file', preview.root, 'contents', 'file-id')
2005
 
        preview.new_directory('directory', preview.root, 'dir-id')
2006
 
        preview_tree = preview.get_preview_tree()
2007
 
        self.assertEqual('file', preview_tree.kind('file-id'))
2008
 
        self.assertEqual('directory', preview_tree.kind('dir-id'))
2009
 
 
2010
 
    def test_get_file_mtime(self):
2011
 
        preview = self.get_empty_preview()
2012
 
        file_trans_id = preview.new_file('file', preview.root, 'contents',
2013
 
                                         'file-id')
2014
 
        limbo_path = preview._limbo_name(file_trans_id)
2015
 
        preview_tree = preview.get_preview_tree()
2016
 
        self.assertEqual(os.stat(limbo_path).st_mtime,
2017
 
                         preview_tree.get_file_mtime('file-id'))
2018
 
 
2019
 
    def test_get_file(self):
2020
 
        preview = self.get_empty_preview()
2021
 
        preview.new_file('file', preview.root, 'contents', 'file-id')
2022
 
        preview_tree = preview.get_preview_tree()
2023
 
        tree_file = preview_tree.get_file('file-id')
2024
 
        try:
2025
 
            self.assertEqual('contents', tree_file.read())
2026
 
        finally:
2027
 
            tree_file.close()
2028
 
 
2029
 
    def test_get_symlink_target(self):
2030
 
        self.requireFeature(SymlinkFeature)
2031
 
        preview = self.get_empty_preview()
2032
 
        preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
2033
 
        preview_tree = preview.get_preview_tree()
2034
 
        self.assertEqual('target',
2035
 
                         preview_tree.get_symlink_target('symlink-id'))