~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Jelmer Vernooij
  • Date: 2009-02-03 15:48:02 UTC
  • mto: (3978.3.6 branch-bzrdir-inter)
  • mto: This revision was merged to the branch mainline in revision 4250.
  • Revision ID: jelmer@samba.org-20090203154802-niup4xrso35yba65
Move most of push to IterGenericBranchBzrDir.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006 Canonical Ltd
 
1
# Copyright (C) 2006, 2007, 2008 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
16
16
 
17
17
import os
18
18
import stat
 
19
from StringIO import StringIO
19
20
import sys
20
21
 
21
22
from bzrlib import (
22
23
    errors,
 
24
    generate_ids,
 
25
    osutils,
 
26
    progress,
 
27
    revision as _mod_revision,
 
28
    symbol_versioning,
23
29
    tests,
24
30
    urlutils,
25
31
    )
26
32
from bzrlib.bzrdir import BzrDir
27
33
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
28
 
                              UnversionedParent, ParentLoop, DeletingParent,)
 
34
                              UnversionedParent, ParentLoop, DeletingParent,
 
35
                              NonDirectoryParent)
 
36
from bzrlib.diff import show_diff_trees
29
37
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
30
38
                           ReusingTransform, CantMoveRoot, 
31
39
                           PathsNotVersionedError, ExistingLimbo,
32
 
                           ImmortalLimbo, LockError)
33
 
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
34
 
from bzrlib.merge import Merge3Merger
35
 
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
 
40
                           ExistingPendingDeletion, ImmortalLimbo,
 
41
                           ImmortalPendingDeletion, LockError)
 
42
from bzrlib.osutils import file_kind, pathjoin
 
43
from bzrlib.merge import Merge3Merger, Merger
 
44
from bzrlib.tests import (
 
45
    HardlinkFeature,
 
46
    SymlinkFeature,
 
47
    TestCase,
 
48
    TestCaseInTempDir,
 
49
    TestSkipped,
 
50
    )
36
51
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
37
52
                              resolve_conflicts, cook_conflicts, 
38
 
                              find_interesting, build_tree, get_backup_name)
39
 
from bzrlib.workingtree import gen_root_id
40
 
 
41
 
 
42
 
class TestTreeTransform(TestCaseInTempDir):
 
53
                              build_tree, get_backup_name,
 
54
                              _FileMover, resolve_checkout,
 
55
                              TransformPreview, create_from_tree)
 
56
from bzrlib.util import bencode
 
57
 
 
58
 
 
59
class TestTreeTransform(tests.TestCaseWithTransport):
43
60
 
44
61
    def setUp(self):
45
62
        super(TestTreeTransform, self).setUp()
46
 
        self.wt = BzrDir.create_standalone_workingtree('.')
 
63
        self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
47
64
        os.chdir('..')
48
65
 
49
66
    def get_transform(self):
50
67
        transform = TreeTransform(self.wt)
51
 
        #self.addCleanup(transform.finalize)
 
68
        self.addCleanup(transform.finalize)
52
69
        return transform, transform.root
53
70
 
54
71
    def test_existing_limbo(self):
55
 
        limbo_name = urlutils.local_path_from_url(
56
 
            self.wt._control_files.controlfilename('limbo'))
57
72
        transform, root = self.get_transform()
 
73
        limbo_name = transform._limbodir
 
74
        deletion_path = transform._deletiondir
58
75
        os.mkdir(pathjoin(limbo_name, 'hehe'))
59
76
        self.assertRaises(ImmortalLimbo, transform.apply)
60
77
        self.assertRaises(LockError, self.wt.unlock)
62
79
        self.assertRaises(LockError, self.wt.unlock)
63
80
        os.rmdir(pathjoin(limbo_name, 'hehe'))
64
81
        os.rmdir(limbo_name)
 
82
        os.rmdir(deletion_path)
65
83
        transform, root = self.get_transform()
66
84
        transform.apply()
67
85
 
 
86
    def test_existing_pending_deletion(self):
 
87
        transform, root = self.get_transform()
 
88
        deletion_path = self._limbodir = urlutils.local_path_from_url(
 
89
            transform._tree._transport.abspath('pending-deletion'))
 
90
        os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
 
91
        self.assertRaises(ImmortalPendingDeletion, transform.apply)
 
92
        self.assertRaises(LockError, self.wt.unlock)
 
93
        self.assertRaises(ExistingPendingDeletion, self.get_transform)
 
94
 
68
95
    def test_build(self):
69
 
        transform, root = self.get_transform() 
 
96
        transform, root = self.get_transform()
 
97
        self.wt.lock_tree_write()
 
98
        self.addCleanup(self.wt.unlock)
70
99
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
71
100
        imaginary_id = transform.trans_id_tree_path('imaginary')
72
101
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
107
136
        transform.finalize()
108
137
        transform.finalize()
109
138
 
 
139
    def test_hardlink(self):
 
140
        self.requireFeature(HardlinkFeature)
 
141
        transform, root = self.get_transform()
 
142
        transform.new_file('file1', root, 'contents')
 
143
        transform.apply()
 
144
        target = self.make_branch_and_tree('target')
 
145
        target_transform = TreeTransform(target)
 
146
        trans_id = target_transform.create_path('file1', target_transform.root)
 
147
        target_transform.create_hardlink(self.wt.abspath('file1'), trans_id)
 
148
        target_transform.apply()
 
149
        self.failUnlessExists('target/file1')
 
150
        source_stat = os.stat(self.wt.abspath('file1'))
 
151
        target_stat = os.stat('target/file1')
 
152
        self.assertEqual(source_stat, target_stat)
 
153
 
110
154
    def test_convenience(self):
111
155
        transform, root = self.get_transform()
 
156
        self.wt.lock_tree_write()
 
157
        self.addCleanup(self.wt.unlock)
112
158
        trans_id = transform.new_file('name', root, 'contents', 
113
159
                                      'my_pretties', True)
114
160
        oz = transform.new_directory('oz', root, 'oz-id')
126
172
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
127
173
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
128
174
 
129
 
        self.assertEqual('toto-contents', 
 
175
        self.assertEqual('toto-contents',
130
176
                         self.wt.get_file_byname('oz/dorothy/toto').read())
131
177
        self.assertIs(self.wt.is_executable('toto-id'), False)
132
178
 
 
179
    def test_tree_reference(self):
 
180
        transform, root = self.get_transform()
 
181
        tree = transform._tree
 
182
        trans_id = transform.new_directory('reference', root, 'subtree-id')
 
183
        transform.set_tree_reference('subtree-revision', trans_id)
 
184
        transform.apply()
 
185
        tree.lock_read()
 
186
        self.addCleanup(tree.unlock)
 
187
        self.assertEqual('subtree-revision',
 
188
                         tree.inventory['subtree-id'].reference_revision)
 
189
 
133
190
    def test_conflicts(self):
134
191
        transform, root = self.get_transform()
135
192
        trans_id = transform.new_file('name', root, 'contents', 
204
261
        transform3.adjust_path('tip', root_id, tip_id)
205
262
        transform3.apply()
206
263
 
 
264
    def test_conflict_on_case_insensitive(self):
 
265
        tree = self.make_branch_and_tree('tree')
 
266
        # Don't try this at home, kids!
 
267
        # Force the tree to report that it is case sensitive, for conflict
 
268
        # resolution tests
 
269
        tree.case_sensitive = True
 
270
        transform = TreeTransform(tree)
 
271
        self.addCleanup(transform.finalize)
 
272
        transform.new_file('file', transform.root, 'content')
 
273
        transform.new_file('FiLe', transform.root, 'content')
 
274
        result = transform.find_conflicts()
 
275
        self.assertEqual([], result)
 
276
        transform.finalize()
 
277
        # Force the tree to report that it is case insensitive, for conflict
 
278
        # generation tests
 
279
        tree.case_sensitive = False
 
280
        transform = TreeTransform(tree)
 
281
        self.addCleanup(transform.finalize)
 
282
        transform.new_file('file', transform.root, 'content')
 
283
        transform.new_file('FiLe', transform.root, 'content')
 
284
        result = transform.find_conflicts()
 
285
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
 
286
 
 
287
    def test_conflict_on_case_insensitive_existing(self):
 
288
        tree = self.make_branch_and_tree('tree')
 
289
        self.build_tree(['tree/FiLe'])
 
290
        # Don't try this at home, kids!
 
291
        # Force the tree to report that it is case sensitive, for conflict
 
292
        # resolution tests
 
293
        tree.case_sensitive = True
 
294
        transform = TreeTransform(tree)
 
295
        self.addCleanup(transform.finalize)
 
296
        transform.new_file('file', transform.root, 'content')
 
297
        result = transform.find_conflicts()
 
298
        self.assertEqual([], result)
 
299
        transform.finalize()
 
300
        # Force the tree to report that it is case insensitive, for conflict
 
301
        # generation tests
 
302
        tree.case_sensitive = False
 
303
        transform = TreeTransform(tree)
 
304
        self.addCleanup(transform.finalize)
 
305
        transform.new_file('file', transform.root, 'content')
 
306
        result = transform.find_conflicts()
 
307
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
 
308
 
 
309
    def test_resolve_case_insensitive_conflict(self):
 
310
        tree = self.make_branch_and_tree('tree')
 
311
        # Don't try this at home, kids!
 
312
        # Force the tree to report that it is case insensitive, for conflict
 
313
        # resolution tests
 
314
        tree.case_sensitive = False
 
315
        transform = TreeTransform(tree)
 
316
        self.addCleanup(transform.finalize)
 
317
        transform.new_file('file', transform.root, 'content')
 
318
        transform.new_file('FiLe', transform.root, 'content')
 
319
        resolve_conflicts(transform)
 
320
        transform.apply()
 
321
        self.failUnlessExists('tree/file')
 
322
        self.failUnlessExists('tree/FiLe.moved')
 
323
 
 
324
    def test_resolve_checkout_case_conflict(self):
 
325
        tree = self.make_branch_and_tree('tree')
 
326
        # Don't try this at home, kids!
 
327
        # Force the tree to report that it is case insensitive, for conflict
 
328
        # resolution tests
 
329
        tree.case_sensitive = False
 
330
        transform = TreeTransform(tree)
 
331
        self.addCleanup(transform.finalize)
 
332
        transform.new_file('file', transform.root, 'content')
 
333
        transform.new_file('FiLe', transform.root, 'content')
 
334
        resolve_conflicts(transform,
 
335
                          pass_func=lambda t, c: resolve_checkout(t, c, []))
 
336
        transform.apply()
 
337
        self.failUnlessExists('tree/file')
 
338
        self.failUnlessExists('tree/FiLe.moved')
 
339
 
 
340
    def test_apply_case_conflict(self):
 
341
        """Ensure that a transform with case conflicts can always be applied"""
 
342
        tree = self.make_branch_and_tree('tree')
 
343
        transform = TreeTransform(tree)
 
344
        self.addCleanup(transform.finalize)
 
345
        transform.new_file('file', transform.root, 'content')
 
346
        transform.new_file('FiLe', transform.root, 'content')
 
347
        dir = transform.new_directory('dir', transform.root)
 
348
        transform.new_file('dirfile', dir, 'content')
 
349
        transform.new_file('dirFiLe', dir, 'content')
 
350
        resolve_conflicts(transform)
 
351
        transform.apply()
 
352
        self.failUnlessExists('tree/file')
 
353
        if not os.path.exists('tree/FiLe.moved'):
 
354
            self.failUnlessExists('tree/FiLe')
 
355
        self.failUnlessExists('tree/dir/dirfile')
 
356
        if not os.path.exists('tree/dir/dirFiLe.moved'):
 
357
            self.failUnlessExists('tree/dir/dirFiLe')
 
358
 
 
359
    def test_case_insensitive_limbo(self):
 
360
        tree = self.make_branch_and_tree('tree')
 
361
        # Don't try this at home, kids!
 
362
        # Force the tree to report that it is case insensitive
 
363
        tree.case_sensitive = False
 
364
        transform = TreeTransform(tree)
 
365
        self.addCleanup(transform.finalize)
 
366
        dir = transform.new_directory('dir', transform.root)
 
367
        first = transform.new_file('file', dir, 'content')
 
368
        second = transform.new_file('FiLe', dir, 'content')
 
369
        self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
 
370
        self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
 
371
 
207
372
    def test_add_del(self):
208
373
        start, root = self.get_transform()
209
374
        start.new_directory('a', root, 'a')
361
526
        replace.apply()
362
527
 
363
528
    def test_symlinks(self):
364
 
        if not has_symlinks():
365
 
            raise TestSkipped('Symlinks are not supported on this platform')
 
529
        self.requireFeature(SymlinkFeature)
366
530
        transform,root = self.get_transform()
367
531
        oz_id = transform.new_directory('oz', root, 'oz-id')
368
532
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
382
546
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
383
547
                         'wizard-target')
384
548
 
 
549
    def test_unable_create_symlink(self):
 
550
        def tt_helper():
 
551
            wt = self.make_branch_and_tree('.')
 
552
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
553
            try:
 
554
                tt.new_symlink('foo', tt.root, 'bar')
 
555
                tt.apply()
 
556
            finally:
 
557
                wt.unlock()
 
558
        os_symlink = getattr(os, 'symlink', None)
 
559
        os.symlink = None
 
560
        try:
 
561
            err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
 
562
            self.assertEquals(
 
563
                "Unable to create symlink 'foo' on this platform",
 
564
                str(err))
 
565
        finally:
 
566
            if os_symlink:
 
567
                os.symlink = os_symlink
385
568
 
386
569
    def get_conflicted(self):
387
570
        create,root = self.get_transform()
472
655
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
473
656
                                         ' oz/emeraldcity.  Cancelled move.')
474
657
 
 
658
    def prepare_wrong_parent_kind(self):
 
659
        tt, root = self.get_transform()
 
660
        tt.new_file('parent', root, 'contents', 'parent-id')
 
661
        tt.apply()
 
662
        tt, root = self.get_transform()
 
663
        parent_id = tt.trans_id_file_id('parent-id')
 
664
        tt.new_file('child,', parent_id, 'contents2', 'file-id')
 
665
        return tt
 
666
 
 
667
    def test_find_conflicts_wrong_parent_kind(self):
 
668
        tt = self.prepare_wrong_parent_kind()
 
669
        tt.find_conflicts()
 
670
 
 
671
    def test_resolve_conflicts_wrong_existing_parent_kind(self):
 
672
        tt = self.prepare_wrong_parent_kind()
 
673
        raw_conflicts = resolve_conflicts(tt)
 
674
        self.assertEqual(set([('non-directory parent', 'Created directory',
 
675
                         'new-3')]), raw_conflicts)
 
676
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
 
677
        self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
 
678
        'parent-id')], cooked_conflicts)
 
679
        tt.apply()
 
680
        self.assertEqual(None, self.wt.path2id('parent'))
 
681
        self.assertEqual('parent-id', self.wt.path2id('parent.new'))
 
682
 
 
683
    def test_resolve_conflicts_wrong_new_parent_kind(self):
 
684
        tt, root = self.get_transform()
 
685
        parent_id = tt.new_directory('parent', root, 'parent-id')
 
686
        tt.new_file('child,', parent_id, 'contents2', 'file-id')
 
687
        tt.apply()
 
688
        tt, root = self.get_transform()
 
689
        parent_id = tt.trans_id_file_id('parent-id')
 
690
        tt.delete_contents(parent_id)
 
691
        tt.create_file('contents', parent_id)
 
692
        raw_conflicts = resolve_conflicts(tt)
 
693
        self.assertEqual(set([('non-directory parent', 'Created directory',
 
694
                         'new-3')]), raw_conflicts)
 
695
        tt.apply()
 
696
        self.assertEqual(None, self.wt.path2id('parent'))
 
697
        self.assertEqual('parent-id', self.wt.path2id('parent.new'))
 
698
 
 
699
    def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
 
700
        tt, root = self.get_transform()
 
701
        parent_id = tt.new_directory('parent', root)
 
702
        tt.new_file('child,', parent_id, 'contents2')
 
703
        tt.apply()
 
704
        tt, root = self.get_transform()
 
705
        parent_id = tt.trans_id_tree_path('parent')
 
706
        tt.delete_contents(parent_id)
 
707
        tt.create_file('contents', parent_id)
 
708
        resolve_conflicts(tt)
 
709
        tt.apply()
 
710
        self.assertIs(None, self.wt.path2id('parent'))
 
711
        self.assertIs(None, self.wt.path2id('parent.new'))
 
712
 
475
713
    def test_moving_versioned_directories(self):
476
714
        create, root = self.get_transform()
477
715
        kansas = create.new_directory('kansas', root, 'kansas-id')
510
748
        rename.set_executability(True, myfile)
511
749
        rename.apply()
512
750
 
513
 
    def test_find_interesting(self):
514
 
        create, root = self.get_transform()
515
 
        wt = create._tree
516
 
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
517
 
        create.new_file('uvfile', root, 'othertext')
518
 
        create.apply()
519
 
        self.assertEqual(find_interesting(wt, wt, ['vfile']),
520
 
                         set(['myfile-id']))
521
 
        self.assertRaises(PathsNotVersionedError, find_interesting, wt, wt,
522
 
                          ['uvfile'])
523
 
 
524
751
    def test_set_executability_order(self):
525
752
        """Ensure that executability behaves the same, no matter what order.
526
753
        
532
759
        """
533
760
        transform, root = self.get_transform()
534
761
        wt = transform._tree
 
762
        wt.lock_read()
 
763
        self.addCleanup(wt.unlock)
535
764
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
536
765
                           True)
537
 
        sac = transform.new_file('set_after_creation', root, 'Set after creation', 'sac')
 
766
        sac = transform.new_file('set_after_creation', root,
 
767
                                 'Set after creation', 'sac')
538
768
        transform.set_executability(True, sac)
539
 
        uws = transform.new_file('unset_without_set', root, 'Unset badly', 'uws')
 
769
        uws = transform.new_file('unset_without_set', root, 'Unset badly',
 
770
                                 'uws')
540
771
        self.assertRaises(KeyError, transform.set_executability, None, uws)
541
772
        transform.apply()
542
773
        self.assertTrue(wt.is_executable('soc'))
549
780
        transform, root = self.get_transform()
550
781
        transform.new_file('file1', root, 'contents', 'file1-id', True)
551
782
        transform.apply()
 
783
        self.wt.lock_write()
 
784
        self.addCleanup(self.wt.unlock)
552
785
        self.assertTrue(self.wt.is_executable('file1-id'))
553
786
        transform, root = self.get_transform()
554
787
        file1_id = transform.trans_id_tree_file_id('file1-id')
587
820
        bar1_abspath = self.wt.abspath('bar')
588
821
        self.assertEqual([bar1_abspath], stat_paths)
589
822
 
 
823
    def test_iter_changes(self):
 
824
        self.wt.set_root_id('eert_toor')
 
825
        transform, root = self.get_transform()
 
826
        transform.new_file('old', root, 'blah', 'id-1', True)
 
827
        transform.apply()
 
828
        transform, root = self.get_transform()
 
829
        try:
 
830
            self.assertEqual([], list(transform.iter_changes()))
 
831
            old = transform.trans_id_tree_file_id('id-1')
 
832
            transform.unversion_file(old)
 
833
            self.assertEqual([('id-1', ('old', None), False, (True, False),
 
834
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
835
                (True, True))], list(transform.iter_changes()))
 
836
            transform.new_directory('new', root, 'id-1')
 
837
            self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
 
838
                ('eert_toor', 'eert_toor'), ('old', 'new'),
 
839
                ('file', 'directory'),
 
840
                (True, False))], list(transform.iter_changes()))
 
841
        finally:
 
842
            transform.finalize()
 
843
 
 
844
    def test_iter_changes_new(self):
 
845
        self.wt.set_root_id('eert_toor')
 
846
        transform, root = self.get_transform()
 
847
        transform.new_file('old', root, 'blah')
 
848
        transform.apply()
 
849
        transform, root = self.get_transform()
 
850
        try:
 
851
            old = transform.trans_id_tree_path('old')
 
852
            transform.version_file('id-1', old)
 
853
            self.assertEqual([('id-1', (None, 'old'), False, (False, True),
 
854
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
855
                (False, False))], list(transform.iter_changes()))
 
856
        finally:
 
857
            transform.finalize()
 
858
 
 
859
    def test_iter_changes_modifications(self):
 
860
        self.wt.set_root_id('eert_toor')
 
861
        transform, root = self.get_transform()
 
862
        transform.new_file('old', root, 'blah', 'id-1')
 
863
        transform.new_file('new', root, 'blah')
 
864
        transform.new_directory('subdir', root, 'subdir-id')
 
865
        transform.apply()
 
866
        transform, root = self.get_transform()
 
867
        try:
 
868
            old = transform.trans_id_tree_path('old')
 
869
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
870
            new = transform.trans_id_tree_path('new')
 
871
            self.assertEqual([], list(transform.iter_changes()))
 
872
 
 
873
            #content deletion
 
874
            transform.delete_contents(old)
 
875
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
876
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
 
877
                (False, False))], list(transform.iter_changes()))
 
878
 
 
879
            #content change
 
880
            transform.create_file('blah', old)
 
881
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
882
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
883
                (False, False))], list(transform.iter_changes()))
 
884
            transform.cancel_deletion(old)
 
885
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
886
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
887
                (False, False))], list(transform.iter_changes()))
 
888
            transform.cancel_creation(old)
 
889
 
 
890
            # move file_id to a different file
 
891
            self.assertEqual([], list(transform.iter_changes()))
 
892
            transform.unversion_file(old)
 
893
            transform.version_file('id-1', new)
 
894
            transform.adjust_path('old', root, new)
 
895
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
896
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
897
                (False, False))], list(transform.iter_changes()))
 
898
            transform.cancel_versioning(new)
 
899
            transform._removed_id = set()
 
900
 
 
901
            #execute bit
 
902
            self.assertEqual([], list(transform.iter_changes()))
 
903
            transform.set_executability(True, old)
 
904
            self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
 
905
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
906
                (False, True))], list(transform.iter_changes()))
 
907
            transform.set_executability(None, old)
 
908
 
 
909
            # filename
 
910
            self.assertEqual([], list(transform.iter_changes()))
 
911
            transform.adjust_path('new', root, old)
 
912
            transform._new_parent = {}
 
913
            self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
 
914
                ('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
 
915
                (False, False))], list(transform.iter_changes()))
 
916
            transform._new_name = {}
 
917
 
 
918
            # parent directory
 
919
            self.assertEqual([], list(transform.iter_changes()))
 
920
            transform.adjust_path('new', subdir, old)
 
921
            transform._new_name = {}
 
922
            self.assertEqual([('id-1', ('old', 'subdir/old'), False,
 
923
                (True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
 
924
                ('file', 'file'), (False, False))],
 
925
                list(transform.iter_changes()))
 
926
            transform._new_path = {}
 
927
 
 
928
        finally:
 
929
            transform.finalize()
 
930
 
 
931
    def test_iter_changes_modified_bleed(self):
 
932
        self.wt.set_root_id('eert_toor')
 
933
        """Modified flag should not bleed from one change to another"""
 
934
        # unfortunately, we have no guarantee that file1 (which is modified)
 
935
        # will be applied before file2.  And if it's applied after file2, it
 
936
        # obviously can't bleed into file2's change output.  But for now, it
 
937
        # works.
 
938
        transform, root = self.get_transform()
 
939
        transform.new_file('file1', root, 'blah', 'id-1')
 
940
        transform.new_file('file2', root, 'blah', 'id-2')
 
941
        transform.apply()
 
942
        transform, root = self.get_transform()
 
943
        try:
 
944
            transform.delete_contents(transform.trans_id_file_id('id-1'))
 
945
            transform.set_executability(True,
 
946
            transform.trans_id_file_id('id-2'))
 
947
            self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
 
948
                ('eert_toor', 'eert_toor'), ('file1', u'file1'),
 
949
                ('file', None), (False, False)),
 
950
                ('id-2', (u'file2', u'file2'), False, (True, True),
 
951
                ('eert_toor', 'eert_toor'), ('file2', u'file2'),
 
952
                ('file', 'file'), (False, True))],
 
953
                list(transform.iter_changes()))
 
954
        finally:
 
955
            transform.finalize()
 
956
 
 
957
    def test_iter_changes_move_missing(self):
 
958
        """Test moving ids with no files around"""
 
959
        self.wt.set_root_id('toor_eert')
 
960
        # Need two steps because versioning a non-existant file is a conflict.
 
961
        transform, root = self.get_transform()
 
962
        transform.new_directory('floater', root, 'floater-id')
 
963
        transform.apply()
 
964
        transform, root = self.get_transform()
 
965
        transform.delete_contents(transform.trans_id_tree_path('floater'))
 
966
        transform.apply()
 
967
        transform, root = self.get_transform()
 
968
        floater = transform.trans_id_tree_path('floater')
 
969
        try:
 
970
            transform.adjust_path('flitter', root, floater)
 
971
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
 
972
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
 
973
            (None, None), (False, False))], list(transform.iter_changes()))
 
974
        finally:
 
975
            transform.finalize()
 
976
 
 
977
    def test_iter_changes_pointless(self):
 
978
        """Ensure that no-ops are not treated as modifications"""
 
979
        self.wt.set_root_id('eert_toor')
 
980
        transform, root = self.get_transform()
 
981
        transform.new_file('old', root, 'blah', 'id-1')
 
982
        transform.new_directory('subdir', root, 'subdir-id')
 
983
        transform.apply()
 
984
        transform, root = self.get_transform()
 
985
        try:
 
986
            old = transform.trans_id_tree_path('old')
 
987
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
988
            self.assertEqual([], list(transform.iter_changes()))
 
989
            transform.delete_contents(subdir)
 
990
            transform.create_directory(subdir)
 
991
            transform.set_executability(False, old)
 
992
            transform.unversion_file(old)
 
993
            transform.version_file('id-1', old)
 
994
            transform.adjust_path('old', root, old)
 
995
            self.assertEqual([], list(transform.iter_changes()))
 
996
        finally:
 
997
            transform.finalize()
 
998
 
 
999
    def test_rename_count(self):
 
1000
        transform, root = self.get_transform()
 
1001
        transform.new_file('name1', root, 'contents')
 
1002
        self.assertEqual(transform.rename_count, 0)
 
1003
        transform.apply()
 
1004
        self.assertEqual(transform.rename_count, 1)
 
1005
        transform2, root = self.get_transform()
 
1006
        transform2.adjust_path('name2', root,
 
1007
                               transform2.trans_id_tree_path('name1'))
 
1008
        self.assertEqual(transform2.rename_count, 0)
 
1009
        transform2.apply()
 
1010
        self.assertEqual(transform2.rename_count, 2)
 
1011
 
 
1012
    def test_change_parent(self):
 
1013
        """Ensure that after we change a parent, the results are still right.
 
1014
 
 
1015
        Renames and parent changes on pending transforms can happen as part
 
1016
        of conflict resolution, and are explicitly permitted by the
 
1017
        TreeTransform API.
 
1018
 
 
1019
        This test ensures they work correctly with the rename-avoidance
 
1020
        optimization.
 
1021
        """
 
1022
        transform, root = self.get_transform()
 
1023
        parent1 = transform.new_directory('parent1', root)
 
1024
        child1 = transform.new_file('child1', parent1, 'contents')
 
1025
        parent2 = transform.new_directory('parent2', root)
 
1026
        transform.adjust_path('child1', parent2, child1)
 
1027
        transform.apply()
 
1028
        self.failIfExists(self.wt.abspath('parent1/child1'))
 
1029
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1030
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
 
1031
        # no rename for child1 (counting only renames during apply)
 
1032
        self.failUnlessEqual(2, transform.rename_count)
 
1033
 
 
1034
    def test_cancel_parent(self):
 
1035
        """Cancelling a parent doesn't cause deletion of a non-empty directory
 
1036
 
 
1037
        This is like the test_change_parent, except that we cancel the parent
 
1038
        before adjusting the path.  The transform must detect that the
 
1039
        directory is non-empty, and move children to safe locations.
 
1040
        """
 
1041
        transform, root = self.get_transform()
 
1042
        parent1 = transform.new_directory('parent1', root)
 
1043
        child1 = transform.new_file('child1', parent1, 'contents')
 
1044
        child2 = transform.new_file('child2', parent1, 'contents')
 
1045
        try:
 
1046
            transform.cancel_creation(parent1)
 
1047
        except OSError:
 
1048
            self.fail('Failed to move child1 before deleting parent1')
 
1049
        transform.cancel_creation(child2)
 
1050
        transform.create_directory(parent1)
 
1051
        try:
 
1052
            transform.cancel_creation(parent1)
 
1053
        # If the transform incorrectly believes that child2 is still in
 
1054
        # parent1's limbo directory, it will try to rename it and fail
 
1055
        # because was already moved by the first cancel_creation.
 
1056
        except OSError:
 
1057
            self.fail('Transform still thinks child2 is a child of parent1')
 
1058
        parent2 = transform.new_directory('parent2', root)
 
1059
        transform.adjust_path('child1', parent2, child1)
 
1060
        transform.apply()
 
1061
        self.failIfExists(self.wt.abspath('parent1'))
 
1062
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1063
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
 
1064
        self.failUnlessEqual(2, transform.rename_count)
 
1065
 
 
1066
    def test_adjust_and_cancel(self):
 
1067
        """Make sure adjust_path keeps track of limbo children properly"""
 
1068
        transform, root = self.get_transform()
 
1069
        parent1 = transform.new_directory('parent1', root)
 
1070
        child1 = transform.new_file('child1', parent1, 'contents')
 
1071
        parent2 = transform.new_directory('parent2', root)
 
1072
        transform.adjust_path('child1', parent2, child1)
 
1073
        transform.cancel_creation(child1)
 
1074
        try:
 
1075
            transform.cancel_creation(parent1)
 
1076
        # if the transform thinks child1 is still in parent1's limbo
 
1077
        # directory, it will attempt to move it and fail.
 
1078
        except OSError:
 
1079
            self.fail('Transform still thinks child1 is a child of parent1')
 
1080
        transform.finalize()
 
1081
 
 
1082
    def test_noname_contents(self):
 
1083
        """TreeTransform should permit deferring naming files."""
 
1084
        transform, root = self.get_transform()
 
1085
        parent = transform.trans_id_file_id('parent-id')
 
1086
        try:
 
1087
            transform.create_directory(parent)
 
1088
        except KeyError:
 
1089
            self.fail("Can't handle contents with no name")
 
1090
        transform.finalize()
 
1091
 
 
1092
    def test_noname_contents_nested(self):
 
1093
        """TreeTransform should permit deferring naming files."""
 
1094
        transform, root = self.get_transform()
 
1095
        parent = transform.trans_id_file_id('parent-id')
 
1096
        try:
 
1097
            transform.create_directory(parent)
 
1098
        except KeyError:
 
1099
            self.fail("Can't handle contents with no name")
 
1100
        child = transform.new_directory('child', parent)
 
1101
        transform.adjust_path('parent', root, parent)
 
1102
        transform.apply()
 
1103
        self.failUnlessExists(self.wt.abspath('parent/child'))
 
1104
        self.assertEqual(1, transform.rename_count)
 
1105
 
 
1106
    def test_reuse_name(self):
 
1107
        """Avoid reusing the same limbo name for different files"""
 
1108
        transform, root = self.get_transform()
 
1109
        parent = transform.new_directory('parent', root)
 
1110
        child1 = transform.new_directory('child', parent)
 
1111
        try:
 
1112
            child2 = transform.new_directory('child', parent)
 
1113
        except OSError:
 
1114
            self.fail('Tranform tried to use the same limbo name twice')
 
1115
        transform.adjust_path('child2', parent, child2)
 
1116
        transform.apply()
 
1117
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
 
1118
        # child2 is put into top-level limbo because child1 has already
 
1119
        # claimed the direct limbo path when child2 is created.  There is no
 
1120
        # advantage in renaming files once they're in top-level limbo, except
 
1121
        # as part of apply.
 
1122
        self.assertEqual(2, transform.rename_count)
 
1123
 
 
1124
    def test_reuse_when_first_moved(self):
 
1125
        """Don't avoid direct paths when it is safe to use them"""
 
1126
        transform, root = self.get_transform()
 
1127
        parent = transform.new_directory('parent', root)
 
1128
        child1 = transform.new_directory('child', parent)
 
1129
        transform.adjust_path('child1', parent, child1)
 
1130
        child2 = transform.new_directory('child', parent)
 
1131
        transform.apply()
 
1132
        # limbo/new-1 => parent
 
1133
        self.assertEqual(1, transform.rename_count)
 
1134
 
 
1135
    def test_reuse_after_cancel(self):
 
1136
        """Don't avoid direct paths when it is safe to use them"""
 
1137
        transform, root = self.get_transform()
 
1138
        parent2 = transform.new_directory('parent2', root)
 
1139
        child1 = transform.new_directory('child1', parent2)
 
1140
        transform.cancel_creation(parent2)
 
1141
        transform.create_directory(parent2)
 
1142
        child2 = transform.new_directory('child1', parent2)
 
1143
        transform.adjust_path('child2', parent2, child1)
 
1144
        transform.apply()
 
1145
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
 
1146
        self.assertEqual(2, transform.rename_count)
 
1147
 
 
1148
    def test_finalize_order(self):
 
1149
        """Finalize must be done in child-to-parent order"""
 
1150
        transform, root = self.get_transform()
 
1151
        parent = transform.new_directory('parent', root)
 
1152
        child = transform.new_directory('child', parent)
 
1153
        try:
 
1154
            transform.finalize()
 
1155
        except OSError:
 
1156
            self.fail('Tried to remove parent before child1')
 
1157
 
 
1158
    def test_cancel_with_cancelled_child_should_succeed(self):
 
1159
        transform, root = self.get_transform()
 
1160
        parent = transform.new_directory('parent', root)
 
1161
        child = transform.new_directory('child', parent)
 
1162
        transform.cancel_creation(child)
 
1163
        transform.cancel_creation(parent)
 
1164
        transform.finalize()
 
1165
 
 
1166
    def test_rollback_on_directory_clash(self):
 
1167
        def tt_helper():
 
1168
            wt = self.make_branch_and_tree('.')
 
1169
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1170
            try:
 
1171
                foo = tt.new_directory('foo', tt.root)
 
1172
                tt.new_file('bar', foo, 'foobar')
 
1173
                baz = tt.new_directory('baz', tt.root)
 
1174
                tt.new_file('qux', baz, 'quux')
 
1175
                # Ask for a rename 'foo' -> 'baz'
 
1176
                tt.adjust_path('baz', tt.root, foo)
 
1177
                # Lie to tt that we've already resolved all conflicts.
 
1178
                tt.apply(no_conflicts=True)
 
1179
            except:
 
1180
                wt.unlock()
 
1181
                raise
 
1182
        # The rename will fail because the target directory is not empty (but
 
1183
        # raises FileExists anyway).
 
1184
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1185
        self.assertContainsRe(str(err),
 
1186
            "^File exists: .+/baz")
 
1187
 
 
1188
    def test_two_directories_clash(self):
 
1189
        def tt_helper():
 
1190
            wt = self.make_branch_and_tree('.')
 
1191
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1192
            try:
 
1193
                foo_1 = tt.new_directory('foo', tt.root)
 
1194
                tt.new_directory('bar', foo_1)
 
1195
                # Adding the same directory with a different content
 
1196
                foo_2 = tt.new_directory('foo', tt.root)
 
1197
                tt.new_directory('baz', foo_2)
 
1198
                # Lie to tt that we've already resolved all conflicts.
 
1199
                tt.apply(no_conflicts=True)
 
1200
            except:
 
1201
                wt.unlock()
 
1202
                raise
 
1203
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1204
        self.assertContainsRe(str(err),
 
1205
            "^File exists: .+/foo")
 
1206
 
 
1207
    def test_two_directories_clash_finalize(self):
 
1208
        def tt_helper():
 
1209
            wt = self.make_branch_and_tree('.')
 
1210
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1211
            try:
 
1212
                foo_1 = tt.new_directory('foo', tt.root)
 
1213
                tt.new_directory('bar', foo_1)
 
1214
                # Adding the same directory with a different content
 
1215
                foo_2 = tt.new_directory('foo', tt.root)
 
1216
                tt.new_directory('baz', foo_2)
 
1217
                # Lie to tt that we've already resolved all conflicts.
 
1218
                tt.apply(no_conflicts=True)
 
1219
            except:
 
1220
                tt.finalize()
 
1221
                raise
 
1222
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1223
        self.assertContainsRe(str(err),
 
1224
            "^File exists: .+/foo")
 
1225
 
 
1226
    def test_file_to_directory(self):
 
1227
        wt = self.make_branch_and_tree('.')
 
1228
        self.build_tree(['foo'])
 
1229
        wt.add(['foo'])
 
1230
        wt.commit("one")
 
1231
        tt = TreeTransform(wt)
 
1232
        self.addCleanup(tt.finalize)
 
1233
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1234
        tt.delete_contents(foo_trans_id)
 
1235
        tt.create_directory(foo_trans_id)
 
1236
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1237
        tt.create_file(["aa\n"], bar_trans_id)
 
1238
        tt.version_file("bar-1", bar_trans_id)
 
1239
        tt.apply()
 
1240
        self.failUnlessExists("foo/bar")
 
1241
        wt.lock_read()
 
1242
        try:
 
1243
            self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1244
                    "directory")
 
1245
        finally:
 
1246
            wt.unlock()
 
1247
        wt.commit("two")
 
1248
        changes = wt.changes_from(wt.basis_tree())
 
1249
        self.assertFalse(changes.has_changed(), changes)
 
1250
 
 
1251
    def test_file_to_symlink(self):
 
1252
        self.requireFeature(SymlinkFeature)
 
1253
        wt = self.make_branch_and_tree('.')
 
1254
        self.build_tree(['foo'])
 
1255
        wt.add(['foo'])
 
1256
        wt.commit("one")
 
1257
        tt = TreeTransform(wt)
 
1258
        self.addCleanup(tt.finalize)
 
1259
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1260
        tt.delete_contents(foo_trans_id)
 
1261
        tt.create_symlink("bar", foo_trans_id)
 
1262
        tt.apply()
 
1263
        self.failUnlessExists("foo")
 
1264
        wt.lock_read()
 
1265
        self.addCleanup(wt.unlock)
 
1266
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1267
                "symlink")
 
1268
 
 
1269
    def test_dir_to_file(self):
 
1270
        wt = self.make_branch_and_tree('.')
 
1271
        self.build_tree(['foo/', 'foo/bar'])
 
1272
        wt.add(['foo', 'foo/bar'])
 
1273
        wt.commit("one")
 
1274
        tt = TreeTransform(wt)
 
1275
        self.addCleanup(tt.finalize)
 
1276
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1277
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1278
        tt.delete_contents(foo_trans_id)
 
1279
        tt.delete_versioned(bar_trans_id)
 
1280
        tt.create_file(["aa\n"], foo_trans_id)
 
1281
        tt.apply()
 
1282
        self.failUnlessExists("foo")
 
1283
        wt.lock_read()
 
1284
        self.addCleanup(wt.unlock)
 
1285
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1286
                "file")
 
1287
 
 
1288
    def test_dir_to_hardlink(self):
 
1289
        self.requireFeature(HardlinkFeature)
 
1290
        wt = self.make_branch_and_tree('.')
 
1291
        self.build_tree(['foo/', 'foo/bar'])
 
1292
        wt.add(['foo', 'foo/bar'])
 
1293
        wt.commit("one")
 
1294
        tt = TreeTransform(wt)
 
1295
        self.addCleanup(tt.finalize)
 
1296
        foo_trans_id = tt.trans_id_tree_path("foo")
 
1297
        bar_trans_id = tt.trans_id_tree_path("foo/bar")
 
1298
        tt.delete_contents(foo_trans_id)
 
1299
        tt.delete_versioned(bar_trans_id)
 
1300
        self.build_tree(['baz'])
 
1301
        tt.create_hardlink("baz", foo_trans_id)
 
1302
        tt.apply()
 
1303
        self.failUnlessExists("foo")
 
1304
        self.failUnlessExists("baz")
 
1305
        wt.lock_read()
 
1306
        self.addCleanup(wt.unlock)
 
1307
        self.assertEqual(wt.inventory.get_file_kind(wt.path2id("foo")),
 
1308
                "file")
 
1309
 
 
1310
    def test_no_final_path(self):
 
1311
        transform, root = self.get_transform()
 
1312
        trans_id = transform.trans_id_file_id('foo')
 
1313
        transform.create_file('bar', trans_id)
 
1314
        transform.cancel_creation(trans_id)
 
1315
        transform.apply()
 
1316
 
 
1317
    def test_create_from_tree(self):
 
1318
        tree1 = self.make_branch_and_tree('tree1')
 
1319
        self.build_tree_contents([('tree1/foo/',), ('tree1/bar', 'baz')])
 
1320
        tree1.add(['foo', 'bar'], ['foo-id', 'bar-id'])
 
1321
        tree2 = self.make_branch_and_tree('tree2')
 
1322
        tt = TreeTransform(tree2)
 
1323
        foo_trans_id = tt.create_path('foo', tt.root)
 
1324
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
 
1325
        bar_trans_id = tt.create_path('bar', tt.root)
 
1326
        create_from_tree(tt, bar_trans_id, tree1, 'bar-id')
 
1327
        tt.apply()
 
1328
        self.assertEqual('directory', osutils.file_kind('tree2/foo'))
 
1329
        self.assertFileEqual('baz', 'tree2/bar')
 
1330
 
 
1331
    def test_create_from_tree_bytes(self):
 
1332
        """Provided lines are used instead of tree content."""
 
1333
        tree1 = self.make_branch_and_tree('tree1')
 
1334
        self.build_tree_contents([('tree1/foo', 'bar'),])
 
1335
        tree1.add('foo', 'foo-id')
 
1336
        tree2 = self.make_branch_and_tree('tree2')
 
1337
        tt = TreeTransform(tree2)
 
1338
        foo_trans_id = tt.create_path('foo', tt.root)
 
1339
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id', bytes='qux')
 
1340
        tt.apply()
 
1341
        self.assertFileEqual('qux', 'tree2/foo')
 
1342
 
 
1343
    def test_create_from_tree_symlink(self):
 
1344
        self.requireFeature(SymlinkFeature)
 
1345
        tree1 = self.make_branch_and_tree('tree1')
 
1346
        os.symlink('bar', 'tree1/foo')
 
1347
        tree1.add('foo', 'foo-id')
 
1348
        tt = TreeTransform(self.make_branch_and_tree('tree2'))
 
1349
        foo_trans_id = tt.create_path('foo', tt.root)
 
1350
        create_from_tree(tt, foo_trans_id, tree1, 'foo-id')
 
1351
        tt.apply()
 
1352
        self.assertEqual('bar', os.readlink('tree2/foo'))
 
1353
 
590
1354
 
591
1355
class TransformGroup(object):
 
1356
 
592
1357
    def __init__(self, dirname, root_id):
593
1358
        self.name = dirname
594
1359
        os.mkdir(dirname)
598
1363
        self.tt = TreeTransform(self.wt)
599
1364
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
600
1365
 
 
1366
 
601
1367
def conflict_text(tree, merge):
602
1368
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
603
1369
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
604
1370
 
605
1371
 
606
1372
class TestTransformMerge(TestCaseInTempDir):
 
1373
 
607
1374
    def test_text_merge(self):
608
 
        root_id = gen_root_id()
 
1375
        root_id = generate_ids.gen_root_id()
609
1376
        base = TransformGroup("base", root_id)
610
1377
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
611
1378
        base.tt.new_file('b', base.root, 'b1', 'b')
639
1406
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
640
1407
        this.tt.apply()
641
1408
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1409
 
642
1410
        # textual merge
643
1411
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
644
1412
        # three-way text conflict
679
1447
        self.assertSubset(merge_modified, modified)
680
1448
        self.assertEqual(len(merge_modified), len(modified))
681
1449
        this.wt.remove('b')
682
 
        this.wt.revert([])
 
1450
        this.wt.revert()
683
1451
 
684
1452
    def test_file_merge(self):
685
 
        if not has_symlinks():
686
 
            raise TestSkipped('Symlinks are not supported on this platform')
687
 
        root_id = gen_root_id()
 
1453
        self.requireFeature(SymlinkFeature)
 
1454
        root_id = generate_ids.gen_root_id()
688
1455
        base = TransformGroup("BASE", root_id)
689
1456
        this = TransformGroup("THIS", root_id)
690
1457
        other = TransformGroup("OTHER", root_id)
725
1492
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
726
1493
 
727
1494
    def test_filename_merge(self):
728
 
        root_id = gen_root_id()
 
1495
        root_id = generate_ids.gen_root_id()
729
1496
        base = TransformGroup("BASE", root_id)
730
1497
        this = TransformGroup("THIS", root_id)
731
1498
        other = TransformGroup("OTHER", root_id)
758
1525
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
759
1526
 
760
1527
    def test_filename_merge_conflicts(self):
761
 
        root_id = gen_root_id()
 
1528
        root_id = generate_ids.gen_root_id()
762
1529
        base = TransformGroup("BASE", root_id)
763
1530
        this = TransformGroup("THIS", root_id)
764
1531
        other = TransformGroup("OTHER", root_id)
791
1558
 
792
1559
class TestBuildTree(tests.TestCaseWithTransport):
793
1560
 
794
 
    def test_build_tree(self):
795
 
        if not has_symlinks():
796
 
            raise TestSkipped('Test requires symlink support')
 
1561
    def test_build_tree_with_symlinks(self):
 
1562
        self.requireFeature(SymlinkFeature)
797
1563
        os.mkdir('a')
798
1564
        a = BzrDir.create_standalone_workingtree('a')
799
1565
        os.mkdir('a/foo')
802
1568
        a.add(['foo', 'foo/bar', 'foo/baz'])
803
1569
        a.commit('initial commit')
804
1570
        b = BzrDir.create_standalone_workingtree('b')
805
 
        build_tree(a.basis_tree(), b)
 
1571
        basis = a.basis_tree()
 
1572
        basis.lock_read()
 
1573
        self.addCleanup(basis.unlock)
 
1574
        build_tree(basis, b)
806
1575
        self.assertIs(os.path.isdir('b/foo'), True)
807
1576
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
808
1577
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
809
1578
 
 
1579
    def test_build_with_references(self):
 
1580
        tree = self.make_branch_and_tree('source',
 
1581
            format='dirstate-with-subtree')
 
1582
        subtree = self.make_branch_and_tree('source/subtree',
 
1583
            format='dirstate-with-subtree')
 
1584
        tree.add_reference(subtree)
 
1585
        tree.commit('a revision')
 
1586
        tree.branch.create_checkout('target')
 
1587
        self.failUnlessExists('target')
 
1588
        self.failUnlessExists('target/subtree')
 
1589
 
810
1590
    def test_file_conflict_handling(self):
811
1591
        """Ensure that when building trees, conflict handling is done"""
812
1592
        source = self.make_branch_and_tree('source')
833
1613
 
834
1614
    def test_symlink_conflict_handling(self):
835
1615
        """Ensure that when building trees, conflict handling is done"""
836
 
        if not has_symlinks():
837
 
            raise TestSkipped('Test requires symlink support')
 
1616
        self.requireFeature(SymlinkFeature)
838
1617
        source = self.make_branch_and_tree('source')
839
1618
        os.symlink('foo', 'source/symlink')
840
1619
        source.add('symlink', 'new-symlink')
914
1693
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
915
1694
            build_tree, source.basis_tree(), target)
916
1695
 
 
1696
    def test_build_tree_rename_count(self):
 
1697
        source = self.make_branch_and_tree('source')
 
1698
        self.build_tree(['source/file1', 'source/dir1/'])
 
1699
        source.add(['file1', 'dir1'])
 
1700
        source.commit('add1')
 
1701
        target1 = self.make_branch_and_tree('target1')
 
1702
        transform_result = build_tree(source.basis_tree(), target1)
 
1703
        self.assertEqual(2, transform_result.rename_count)
 
1704
 
 
1705
        self.build_tree(['source/dir1/file2'])
 
1706
        source.add(['dir1/file2'])
 
1707
        source.commit('add3')
 
1708
        target2 = self.make_branch_and_tree('target2')
 
1709
        transform_result = build_tree(source.basis_tree(), target2)
 
1710
        # children of non-root directories should not be renamed
 
1711
        self.assertEqual(2, transform_result.rename_count)
 
1712
 
 
1713
    def create_ab_tree(self):
 
1714
        """Create a committed test tree with two files"""
 
1715
        source = self.make_branch_and_tree('source')
 
1716
        self.build_tree_contents([('source/file1', 'A')])
 
1717
        self.build_tree_contents([('source/file2', 'B')])
 
1718
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
 
1719
        source.commit('commit files')
 
1720
        source.lock_write()
 
1721
        self.addCleanup(source.unlock)
 
1722
        return source
 
1723
 
 
1724
    def test_build_tree_accelerator_tree(self):
 
1725
        source = self.create_ab_tree()
 
1726
        self.build_tree_contents([('source/file2', 'C')])
 
1727
        calls = []
 
1728
        real_source_get_file = source.get_file
 
1729
        def get_file(file_id, path=None):
 
1730
            calls.append(file_id)
 
1731
            return real_source_get_file(file_id, path)
 
1732
        source.get_file = get_file
 
1733
        target = self.make_branch_and_tree('target')
 
1734
        revision_tree = source.basis_tree()
 
1735
        revision_tree.lock_read()
 
1736
        self.addCleanup(revision_tree.unlock)
 
1737
        build_tree(revision_tree, target, source)
 
1738
        self.assertEqual(['file1-id'], calls)
 
1739
        target.lock_read()
 
1740
        self.addCleanup(target.unlock)
 
1741
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1742
 
 
1743
    def test_build_tree_accelerator_tree_missing_file(self):
 
1744
        source = self.create_ab_tree()
 
1745
        os.unlink('source/file1')
 
1746
        source.remove(['file2'])
 
1747
        target = self.make_branch_and_tree('target')
 
1748
        revision_tree = source.basis_tree()
 
1749
        revision_tree.lock_read()
 
1750
        self.addCleanup(revision_tree.unlock)
 
1751
        build_tree(revision_tree, target, source)
 
1752
        target.lock_read()
 
1753
        self.addCleanup(target.unlock)
 
1754
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1755
 
 
1756
    def test_build_tree_accelerator_wrong_kind(self):
 
1757
        self.requireFeature(SymlinkFeature)
 
1758
        source = self.make_branch_and_tree('source')
 
1759
        self.build_tree_contents([('source/file1', '')])
 
1760
        self.build_tree_contents([('source/file2', '')])
 
1761
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
 
1762
        source.commit('commit files')
 
1763
        os.unlink('source/file2')
 
1764
        self.build_tree_contents([('source/file2/', 'C')])
 
1765
        os.unlink('source/file1')
 
1766
        os.symlink('file2', 'source/file1')
 
1767
        calls = []
 
1768
        real_source_get_file = source.get_file
 
1769
        def get_file(file_id, path=None):
 
1770
            calls.append(file_id)
 
1771
            return real_source_get_file(file_id, path)
 
1772
        source.get_file = get_file
 
1773
        target = self.make_branch_and_tree('target')
 
1774
        revision_tree = source.basis_tree()
 
1775
        revision_tree.lock_read()
 
1776
        self.addCleanup(revision_tree.unlock)
 
1777
        build_tree(revision_tree, target, source)
 
1778
        self.assertEqual([], calls)
 
1779
        target.lock_read()
 
1780
        self.addCleanup(target.unlock)
 
1781
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1782
 
 
1783
    def test_build_tree_hardlink(self):
 
1784
        self.requireFeature(HardlinkFeature)
 
1785
        source = self.create_ab_tree()
 
1786
        target = self.make_branch_and_tree('target')
 
1787
        revision_tree = source.basis_tree()
 
1788
        revision_tree.lock_read()
 
1789
        self.addCleanup(revision_tree.unlock)
 
1790
        build_tree(revision_tree, target, source, hardlink=True)
 
1791
        target.lock_read()
 
1792
        self.addCleanup(target.unlock)
 
1793
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1794
        source_stat = os.stat('source/file1')
 
1795
        target_stat = os.stat('target/file1')
 
1796
        self.assertEqual(source_stat, target_stat)
 
1797
 
 
1798
        # Explicitly disallowing hardlinks should prevent them.
 
1799
        target2 = self.make_branch_and_tree('target2')
 
1800
        build_tree(revision_tree, target2, source, hardlink=False)
 
1801
        target2.lock_read()
 
1802
        self.addCleanup(target2.unlock)
 
1803
        self.assertEqual([], list(target2.iter_changes(revision_tree)))
 
1804
        source_stat = os.stat('source/file1')
 
1805
        target2_stat = os.stat('target2/file1')
 
1806
        self.assertNotEqual(source_stat, target2_stat)
 
1807
 
 
1808
    def test_build_tree_accelerator_tree_moved(self):
 
1809
        source = self.make_branch_and_tree('source')
 
1810
        self.build_tree_contents([('source/file1', 'A')])
 
1811
        source.add(['file1'], ['file1-id'])
 
1812
        source.commit('commit files')
 
1813
        source.rename_one('file1', 'file2')
 
1814
        source.lock_read()
 
1815
        self.addCleanup(source.unlock)
 
1816
        target = self.make_branch_and_tree('target')
 
1817
        revision_tree = source.basis_tree()
 
1818
        revision_tree.lock_read()
 
1819
        self.addCleanup(revision_tree.unlock)
 
1820
        build_tree(revision_tree, target, source)
 
1821
        target.lock_read()
 
1822
        self.addCleanup(target.unlock)
 
1823
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1824
 
 
1825
    def test_build_tree_hardlinks_preserve_execute(self):
 
1826
        self.requireFeature(HardlinkFeature)
 
1827
        source = self.create_ab_tree()
 
1828
        tt = TreeTransform(source)
 
1829
        trans_id = tt.trans_id_tree_file_id('file1-id')
 
1830
        tt.set_executability(True, trans_id)
 
1831
        tt.apply()
 
1832
        self.assertTrue(source.is_executable('file1-id'))
 
1833
        target = self.make_branch_and_tree('target')
 
1834
        revision_tree = source.basis_tree()
 
1835
        revision_tree.lock_read()
 
1836
        self.addCleanup(revision_tree.unlock)
 
1837
        build_tree(revision_tree, target, source, hardlink=True)
 
1838
        target.lock_read()
 
1839
        self.addCleanup(target.unlock)
 
1840
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
1841
        self.assertTrue(source.is_executable('file1-id'))
 
1842
 
 
1843
    def test_case_insensitive_build_tree_inventory(self):
 
1844
        source = self.make_branch_and_tree('source')
 
1845
        self.build_tree(['source/file', 'source/FILE'])
 
1846
        source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
 
1847
        source.commit('added files')
 
1848
        # Don't try this at home, kids!
 
1849
        # Force the tree to report that it is case insensitive
 
1850
        target = self.make_branch_and_tree('target')
 
1851
        target.case_sensitive = False
 
1852
        build_tree(source.basis_tree(), target, source, delta_from_tree=True)
 
1853
        self.assertEqual('file.moved', target.id2path('lower-id'))
 
1854
        self.assertEqual('FILE', target.id2path('upper-id'))
 
1855
 
917
1856
 
918
1857
class MockTransform(object):
919
1858
 
926
1865
                return True
927
1866
        return False
928
1867
 
 
1868
 
929
1869
class MockEntry(object):
930
1870
    def __init__(self):
931
1871
        object.__init__(self)
932
1872
        self.name = "name"
933
1873
 
 
1874
 
934
1875
class TestGetBackupName(TestCase):
935
1876
    def test_get_backup_name(self):
936
1877
        tt = MockTransform()
944
1885
        self.assertEqual(name, 'name.~1~')
945
1886
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
946
1887
        self.assertEqual(name, 'name.~4~')
 
1888
 
 
1889
 
 
1890
class TestFileMover(tests.TestCaseWithTransport):
 
1891
 
 
1892
    def test_file_mover(self):
 
1893
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
 
1894
        mover = _FileMover()
 
1895
        mover.rename('a', 'q')
 
1896
        self.failUnlessExists('q')
 
1897
        self.failIfExists('a')
 
1898
        self.failUnlessExists('q/b')
 
1899
        self.failUnlessExists('c')
 
1900
        self.failUnlessExists('c/d')
 
1901
 
 
1902
    def test_pre_delete_rollback(self):
 
1903
        self.build_tree(['a/'])
 
1904
        mover = _FileMover()
 
1905
        mover.pre_delete('a', 'q')
 
1906
        self.failUnlessExists('q')
 
1907
        self.failIfExists('a')
 
1908
        mover.rollback()
 
1909
        self.failIfExists('q')
 
1910
        self.failUnlessExists('a')
 
1911
 
 
1912
    def test_apply_deletions(self):
 
1913
        self.build_tree(['a/', 'b/'])
 
1914
        mover = _FileMover()
 
1915
        mover.pre_delete('a', 'q')
 
1916
        mover.pre_delete('b', 'r')
 
1917
        self.failUnlessExists('q')
 
1918
        self.failUnlessExists('r')
 
1919
        self.failIfExists('a')
 
1920
        self.failIfExists('b')
 
1921
        mover.apply_deletions()
 
1922
        self.failIfExists('q')
 
1923
        self.failIfExists('r')
 
1924
        self.failIfExists('a')
 
1925
        self.failIfExists('b')
 
1926
 
 
1927
    def test_file_mover_rollback(self):
 
1928
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
 
1929
        mover = _FileMover()
 
1930
        mover.rename('c/d', 'c/f')
 
1931
        mover.rename('c/e', 'c/d')
 
1932
        try:
 
1933
            mover.rename('a', 'c')
 
1934
        except errors.FileExists, e:
 
1935
            mover.rollback()
 
1936
        self.failUnlessExists('a')
 
1937
        self.failUnlessExists('c/d')
 
1938
 
 
1939
 
 
1940
class Bogus(Exception):
 
1941
    pass
 
1942
 
 
1943
 
 
1944
class TestTransformRollback(tests.TestCaseWithTransport):
 
1945
 
 
1946
    class ExceptionFileMover(_FileMover):
 
1947
 
 
1948
        def __init__(self, bad_source=None, bad_target=None):
 
1949
            _FileMover.__init__(self)
 
1950
            self.bad_source = bad_source
 
1951
            self.bad_target = bad_target
 
1952
 
 
1953
        def rename(self, source, target):
 
1954
            if (self.bad_source is not None and
 
1955
                source.endswith(self.bad_source)):
 
1956
                raise Bogus
 
1957
            elif (self.bad_target is not None and
 
1958
                target.endswith(self.bad_target)):
 
1959
                raise Bogus
 
1960
            else:
 
1961
                _FileMover.rename(self, source, target)
 
1962
 
 
1963
    def test_rollback_rename(self):
 
1964
        tree = self.make_branch_and_tree('.')
 
1965
        self.build_tree(['a/', 'a/b'])
 
1966
        tt = TreeTransform(tree)
 
1967
        self.addCleanup(tt.finalize)
 
1968
        a_id = tt.trans_id_tree_path('a')
 
1969
        tt.adjust_path('c', tt.root, a_id)
 
1970
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1971
        self.assertRaises(Bogus, tt.apply,
 
1972
                          _mover=self.ExceptionFileMover(bad_source='a'))
 
1973
        self.failUnlessExists('a')
 
1974
        self.failUnlessExists('a/b')
 
1975
        tt.apply()
 
1976
        self.failUnlessExists('c')
 
1977
        self.failUnlessExists('c/d')
 
1978
 
 
1979
    def test_rollback_rename_into_place(self):
 
1980
        tree = self.make_branch_and_tree('.')
 
1981
        self.build_tree(['a/', 'a/b'])
 
1982
        tt = TreeTransform(tree)
 
1983
        self.addCleanup(tt.finalize)
 
1984
        a_id = tt.trans_id_tree_path('a')
 
1985
        tt.adjust_path('c', tt.root, a_id)
 
1986
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1987
        self.assertRaises(Bogus, tt.apply,
 
1988
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
 
1989
        self.failUnlessExists('a')
 
1990
        self.failUnlessExists('a/b')
 
1991
        tt.apply()
 
1992
        self.failUnlessExists('c')
 
1993
        self.failUnlessExists('c/d')
 
1994
 
 
1995
    def test_rollback_deletion(self):
 
1996
        tree = self.make_branch_and_tree('.')
 
1997
        self.build_tree(['a/', 'a/b'])
 
1998
        tt = TreeTransform(tree)
 
1999
        self.addCleanup(tt.finalize)
 
2000
        a_id = tt.trans_id_tree_path('a')
 
2001
        tt.delete_contents(a_id)
 
2002
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
 
2003
        self.assertRaises(Bogus, tt.apply,
 
2004
                          _mover=self.ExceptionFileMover(bad_target='d'))
 
2005
        self.failUnlessExists('a')
 
2006
        self.failUnlessExists('a/b')
 
2007
 
 
2008
    def test_resolve_no_parent(self):
 
2009
        wt = self.make_branch_and_tree('.')
 
2010
        tt = TreeTransform(wt)
 
2011
        self.addCleanup(tt.finalize)
 
2012
        parent = tt.trans_id_file_id('parent-id')
 
2013
        tt.new_file('file', parent, 'Contents')
 
2014
        resolve_conflicts(tt)
 
2015
 
 
2016
 
 
2017
A_ENTRY = ('a-id', ('a', 'a'), True, (True, True),
 
2018
                  ('TREE_ROOT', 'TREE_ROOT'), ('a', 'a'), ('file', 'file'),
 
2019
                  (False, False))
 
2020
ROOT_ENTRY = ('TREE_ROOT', ('', ''), False, (True, True), (None, None),
 
2021
              ('', ''), ('directory', 'directory'), (False, None))
 
2022
 
 
2023
 
 
2024
class TestTransformPreview(tests.TestCaseWithTransport):
 
2025
 
 
2026
    def create_tree(self):
 
2027
        tree = self.make_branch_and_tree('.')
 
2028
        self.build_tree_contents([('a', 'content 1')])
 
2029
        tree.add('a', 'a-id')
 
2030
        tree.commit('rev1', rev_id='rev1')
 
2031
        return tree.branch.repository.revision_tree('rev1')
 
2032
 
 
2033
    def get_empty_preview(self):
 
2034
        repository = self.make_repository('repo')
 
2035
        tree = repository.revision_tree(_mod_revision.NULL_REVISION)
 
2036
        preview = TransformPreview(tree)
 
2037
        self.addCleanup(preview.finalize)
 
2038
        return preview
 
2039
 
 
2040
    def test_transform_preview(self):
 
2041
        revision_tree = self.create_tree()
 
2042
        preview = TransformPreview(revision_tree)
 
2043
        self.addCleanup(preview.finalize)
 
2044
 
 
2045
    def test_transform_preview_tree(self):
 
2046
        revision_tree = self.create_tree()
 
2047
        preview = TransformPreview(revision_tree)
 
2048
        self.addCleanup(preview.finalize)
 
2049
        preview.get_preview_tree()
 
2050
 
 
2051
    def test_transform_new_file(self):
 
2052
        revision_tree = self.create_tree()
 
2053
        preview = TransformPreview(revision_tree)
 
2054
        self.addCleanup(preview.finalize)
 
2055
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
2056
        preview_tree = preview.get_preview_tree()
 
2057
        self.assertEqual(preview_tree.kind('file2-id'), 'file')
 
2058
        self.assertEqual(
 
2059
            preview_tree.get_file('file2-id').read(), 'content B\n')
 
2060
 
 
2061
    def test_diff_preview_tree(self):
 
2062
        revision_tree = self.create_tree()
 
2063
        preview = TransformPreview(revision_tree)
 
2064
        self.addCleanup(preview.finalize)
 
2065
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
2066
        preview_tree = preview.get_preview_tree()
 
2067
        out = StringIO()
 
2068
        show_diff_trees(revision_tree, preview_tree, out)
 
2069
        lines = out.getvalue().splitlines()
 
2070
        self.assertEqual(lines[0], "=== added file 'file2'")
 
2071
        # 3 lines of diff administrivia
 
2072
        self.assertEqual(lines[4], "+content B")
 
2073
 
 
2074
    def test_transform_conflicts(self):
 
2075
        revision_tree = self.create_tree()
 
2076
        preview = TransformPreview(revision_tree)
 
2077
        self.addCleanup(preview.finalize)
 
2078
        preview.new_file('a', preview.root, 'content 2')
 
2079
        resolve_conflicts(preview)
 
2080
        trans_id = preview.trans_id_file_id('a-id')
 
2081
        self.assertEqual('a.moved', preview.final_name(trans_id))
 
2082
 
 
2083
    def get_tree_and_preview_tree(self):
 
2084
        revision_tree = self.create_tree()
 
2085
        preview = TransformPreview(revision_tree)
 
2086
        self.addCleanup(preview.finalize)
 
2087
        a_trans_id = preview.trans_id_file_id('a-id')
 
2088
        preview.delete_contents(a_trans_id)
 
2089
        preview.create_file('b content', a_trans_id)
 
2090
        preview_tree = preview.get_preview_tree()
 
2091
        return revision_tree, preview_tree
 
2092
 
 
2093
    def test_iter_changes(self):
 
2094
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2095
        root = revision_tree.inventory.root.file_id
 
2096
        self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
 
2097
                          (root, root), ('a', 'a'), ('file', 'file'),
 
2098
                          (False, False))],
 
2099
                          list(preview_tree.iter_changes(revision_tree)))
 
2100
 
 
2101
    def test_include_unchanged_succeeds(self):
 
2102
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2103
        changes = preview_tree.iter_changes(revision_tree,
 
2104
                                            include_unchanged=True)
 
2105
        root = revision_tree.inventory.root.file_id
 
2106
 
 
2107
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2108
 
 
2109
    def test_specific_files(self):
 
2110
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2111
        changes = preview_tree.iter_changes(revision_tree,
 
2112
                                            specific_files=[''])
 
2113
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2114
 
 
2115
    def test_want_unversioned(self):
 
2116
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2117
        changes = preview_tree.iter_changes(revision_tree,
 
2118
                                            want_unversioned=True)
 
2119
        self.assertEqual([ROOT_ENTRY, A_ENTRY], list(changes))
 
2120
 
 
2121
    def test_ignore_extra_trees_no_specific_files(self):
 
2122
        # extra_trees is harmless without specific_files, so we'll silently
 
2123
        # accept it, even though we won't use it.
 
2124
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2125
        preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])
 
2126
 
 
2127
    def test_ignore_require_versioned_no_specific_files(self):
 
2128
        # require_versioned is meaningless without specific_files.
 
2129
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2130
        preview_tree.iter_changes(revision_tree, require_versioned=False)
 
2131
 
 
2132
    def test_ignore_pb(self):
 
2133
        # pb could be supported, but TT.iter_changes doesn't support it.
 
2134
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2135
        preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
 
2136
 
 
2137
    def test_kind(self):
 
2138
        revision_tree = self.create_tree()
 
2139
        preview = TransformPreview(revision_tree)
 
2140
        self.addCleanup(preview.finalize)
 
2141
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
2142
        preview.new_directory('directory', preview.root, 'dir-id')
 
2143
        preview_tree = preview.get_preview_tree()
 
2144
        self.assertEqual('file', preview_tree.kind('file-id'))
 
2145
        self.assertEqual('directory', preview_tree.kind('dir-id'))
 
2146
 
 
2147
    def test_get_file_mtime(self):
 
2148
        preview = self.get_empty_preview()
 
2149
        file_trans_id = preview.new_file('file', preview.root, 'contents',
 
2150
                                         'file-id')
 
2151
        limbo_path = preview._limbo_name(file_trans_id)
 
2152
        preview_tree = preview.get_preview_tree()
 
2153
        self.assertEqual(os.stat(limbo_path).st_mtime,
 
2154
                         preview_tree.get_file_mtime('file-id'))
 
2155
 
 
2156
    def test_get_file(self):
 
2157
        preview = self.get_empty_preview()
 
2158
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
2159
        preview_tree = preview.get_preview_tree()
 
2160
        tree_file = preview_tree.get_file('file-id')
 
2161
        try:
 
2162
            self.assertEqual('contents', tree_file.read())
 
2163
        finally:
 
2164
            tree_file.close()
 
2165
 
 
2166
    def test_get_symlink_target(self):
 
2167
        self.requireFeature(SymlinkFeature)
 
2168
        preview = self.get_empty_preview()
 
2169
        preview.new_symlink('symlink', preview.root, 'target', 'symlink-id')
 
2170
        preview_tree = preview.get_preview_tree()
 
2171
        self.assertEqual('target',
 
2172
                         preview_tree.get_symlink_target('symlink-id'))
 
2173
 
 
2174
    def test_all_file_ids(self):
 
2175
        tree = self.make_branch_and_tree('tree')
 
2176
        self.build_tree(['tree/a', 'tree/b', 'tree/c'])
 
2177
        tree.add(['a', 'b', 'c'], ['a-id', 'b-id', 'c-id'])
 
2178
        preview = TransformPreview(tree)
 
2179
        self.addCleanup(preview.finalize)
 
2180
        preview.unversion_file(preview.trans_id_file_id('b-id'))
 
2181
        c_trans_id = preview.trans_id_file_id('c-id')
 
2182
        preview.unversion_file(c_trans_id)
 
2183
        preview.version_file('c-id', c_trans_id)
 
2184
        preview_tree = preview.get_preview_tree()
 
2185
        self.assertEqual(set(['a-id', 'c-id', tree.get_root_id()]),
 
2186
                         preview_tree.all_file_ids())
 
2187
 
 
2188
    def test_path2id_deleted_unchanged(self):
 
2189
        tree = self.make_branch_and_tree('tree')
 
2190
        self.build_tree(['tree/unchanged', 'tree/deleted'])
 
2191
        tree.add(['unchanged', 'deleted'], ['unchanged-id', 'deleted-id'])
 
2192
        preview = TransformPreview(tree)
 
2193
        self.addCleanup(preview.finalize)
 
2194
        preview.unversion_file(preview.trans_id_file_id('deleted-id'))
 
2195
        preview_tree = preview.get_preview_tree()
 
2196
        self.assertEqual('unchanged-id', preview_tree.path2id('unchanged'))
 
2197
        self.assertIs(None, preview_tree.path2id('deleted'))
 
2198
 
 
2199
    def test_path2id_created(self):
 
2200
        tree = self.make_branch_and_tree('tree')
 
2201
        self.build_tree(['tree/unchanged'])
 
2202
        tree.add(['unchanged'], ['unchanged-id'])
 
2203
        preview = TransformPreview(tree)
 
2204
        self.addCleanup(preview.finalize)
 
2205
        preview.new_file('new', preview.trans_id_file_id('unchanged-id'),
 
2206
            'contents', 'new-id')
 
2207
        preview_tree = preview.get_preview_tree()
 
2208
        self.assertEqual('new-id', preview_tree.path2id('unchanged/new'))
 
2209
 
 
2210
    def test_path2id_moved(self):
 
2211
        tree = self.make_branch_and_tree('tree')
 
2212
        self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
 
2213
        tree.add(['old_parent', 'old_parent/child'],
 
2214
                 ['old_parent-id', 'child-id'])
 
2215
        preview = TransformPreview(tree)
 
2216
        self.addCleanup(preview.finalize)
 
2217
        new_parent = preview.new_directory('new_parent', preview.root,
 
2218
                                           'new_parent-id')
 
2219
        preview.adjust_path('child', new_parent,
 
2220
                            preview.trans_id_file_id('child-id'))
 
2221
        preview_tree = preview.get_preview_tree()
 
2222
        self.assertIs(None, preview_tree.path2id('old_parent/child'))
 
2223
        self.assertEqual('child-id', preview_tree.path2id('new_parent/child'))
 
2224
 
 
2225
    def test_path2id_renamed_parent(self):
 
2226
        tree = self.make_branch_and_tree('tree')
 
2227
        self.build_tree(['tree/old_name/', 'tree/old_name/child'])
 
2228
        tree.add(['old_name', 'old_name/child'],
 
2229
                 ['parent-id', 'child-id'])
 
2230
        preview = TransformPreview(tree)
 
2231
        self.addCleanup(preview.finalize)
 
2232
        preview.adjust_path('new_name', preview.root,
 
2233
                            preview.trans_id_file_id('parent-id'))
 
2234
        preview_tree = preview.get_preview_tree()
 
2235
        self.assertIs(None, preview_tree.path2id('old_name/child'))
 
2236
        self.assertEqual('child-id', preview_tree.path2id('new_name/child'))
 
2237
 
 
2238
    def assertMatchingIterEntries(self, tt, specific_file_ids=None):
 
2239
        preview_tree = tt.get_preview_tree()
 
2240
        preview_result = list(preview_tree.iter_entries_by_dir(
 
2241
                              specific_file_ids))
 
2242
        tree = tt._tree
 
2243
        tt.apply()
 
2244
        actual_result = list(tree.iter_entries_by_dir(specific_file_ids))
 
2245
        self.assertEqual(actual_result, preview_result)
 
2246
 
 
2247
    def test_iter_entries_by_dir_new(self):
 
2248
        tree = self.make_branch_and_tree('tree')
 
2249
        tt = TreeTransform(tree)
 
2250
        tt.new_file('new', tt.root, 'contents', 'new-id')
 
2251
        self.assertMatchingIterEntries(tt)
 
2252
 
 
2253
    def test_iter_entries_by_dir_deleted(self):
 
2254
        tree = self.make_branch_and_tree('tree')
 
2255
        self.build_tree(['tree/deleted'])
 
2256
        tree.add('deleted', 'deleted-id')
 
2257
        tt = TreeTransform(tree)
 
2258
        tt.delete_contents(tt.trans_id_file_id('deleted-id'))
 
2259
        self.assertMatchingIterEntries(tt)
 
2260
 
 
2261
    def test_iter_entries_by_dir_unversioned(self):
 
2262
        tree = self.make_branch_and_tree('tree')
 
2263
        self.build_tree(['tree/removed'])
 
2264
        tree.add('removed', 'removed-id')
 
2265
        tt = TreeTransform(tree)
 
2266
        tt.unversion_file(tt.trans_id_file_id('removed-id'))
 
2267
        self.assertMatchingIterEntries(tt)
 
2268
 
 
2269
    def test_iter_entries_by_dir_moved(self):
 
2270
        tree = self.make_branch_and_tree('tree')
 
2271
        self.build_tree(['tree/moved', 'tree/new_parent/'])
 
2272
        tree.add(['moved', 'new_parent'], ['moved-id', 'new_parent-id'])
 
2273
        tt = TreeTransform(tree)
 
2274
        tt.adjust_path('moved', tt.trans_id_file_id('new_parent-id'),
 
2275
                       tt.trans_id_file_id('moved-id'))
 
2276
        self.assertMatchingIterEntries(tt)
 
2277
 
 
2278
    def test_iter_entries_by_dir_specific_file_ids(self):
 
2279
        tree = self.make_branch_and_tree('tree')
 
2280
        tree.set_root_id('tree-root-id')
 
2281
        self.build_tree(['tree/parent/', 'tree/parent/child'])
 
2282
        tree.add(['parent', 'parent/child'], ['parent-id', 'child-id'])
 
2283
        tt = TreeTransform(tree)
 
2284
        self.assertMatchingIterEntries(tt, ['tree-root-id', 'child-id'])
 
2285
 
 
2286
    def test_symlink_content_summary(self):
 
2287
        self.requireFeature(SymlinkFeature)
 
2288
        preview = self.get_empty_preview()
 
2289
        preview.new_symlink('path', preview.root, 'target', 'path-id')
 
2290
        summary = preview.get_preview_tree().path_content_summary('path')
 
2291
        self.assertEqual(('symlink', None, None, 'target'), summary)
 
2292
 
 
2293
    def test_missing_content_summary(self):
 
2294
        preview = self.get_empty_preview()
 
2295
        summary = preview.get_preview_tree().path_content_summary('path')
 
2296
        self.assertEqual(('missing', None, None, None), summary)
 
2297
 
 
2298
    def test_deleted_content_summary(self):
 
2299
        tree = self.make_branch_and_tree('tree')
 
2300
        self.build_tree(['tree/path/'])
 
2301
        tree.add('path')
 
2302
        preview = TransformPreview(tree)
 
2303
        self.addCleanup(preview.finalize)
 
2304
        preview.delete_contents(preview.trans_id_tree_path('path'))
 
2305
        summary = preview.get_preview_tree().path_content_summary('path')
 
2306
        self.assertEqual(('missing', None, None, None), summary)
 
2307
 
 
2308
    def test_file_content_summary_executable(self):
 
2309
        if not osutils.supports_executable():
 
2310
            raise TestNotApplicable()
 
2311
        preview = self.get_empty_preview()
 
2312
        path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
 
2313
        preview.set_executability(True, path_id)
 
2314
        summary = preview.get_preview_tree().path_content_summary('path')
 
2315
        self.assertEqual(4, len(summary))
 
2316
        self.assertEqual('file', summary[0])
 
2317
        # size must be known
 
2318
        self.assertEqual(len('contents'), summary[1])
 
2319
        # executable
 
2320
        self.assertEqual(True, summary[2])
 
2321
        # will not have hash (not cheap to determine)
 
2322
        self.assertIs(None, summary[3])
 
2323
 
 
2324
    def test_change_executability(self):
 
2325
        if not osutils.supports_executable():
 
2326
            raise TestNotApplicable()
 
2327
        tree = self.make_branch_and_tree('tree')
 
2328
        self.build_tree(['tree/path'])
 
2329
        tree.add('path')
 
2330
        preview = TransformPreview(tree)
 
2331
        self.addCleanup(preview.finalize)
 
2332
        path_id = preview.trans_id_tree_path('path')
 
2333
        preview.set_executability(True, path_id)
 
2334
        summary = preview.get_preview_tree().path_content_summary('path')
 
2335
        self.assertEqual(True, summary[2])
 
2336
 
 
2337
    def test_file_content_summary_non_exec(self):
 
2338
        preview = self.get_empty_preview()
 
2339
        preview.new_file('path', preview.root, 'contents', 'path-id')
 
2340
        summary = preview.get_preview_tree().path_content_summary('path')
 
2341
        self.assertEqual(4, len(summary))
 
2342
        self.assertEqual('file', summary[0])
 
2343
        # size must be known
 
2344
        self.assertEqual(len('contents'), summary[1])
 
2345
        # not executable
 
2346
        if osutils.supports_executable():
 
2347
            self.assertEqual(False, summary[2])
 
2348
        else:
 
2349
            self.assertEqual(None, summary[2])
 
2350
        # will not have hash (not cheap to determine)
 
2351
        self.assertIs(None, summary[3])
 
2352
 
 
2353
    def test_dir_content_summary(self):
 
2354
        preview = self.get_empty_preview()
 
2355
        preview.new_directory('path', preview.root, 'path-id')
 
2356
        summary = preview.get_preview_tree().path_content_summary('path')
 
2357
        self.assertEqual(('directory', None, None, None), summary)
 
2358
 
 
2359
    def test_tree_content_summary(self):
 
2360
        preview = self.get_empty_preview()
 
2361
        path = preview.new_directory('path', preview.root, 'path-id')
 
2362
        preview.set_tree_reference('rev-1', path)
 
2363
        summary = preview.get_preview_tree().path_content_summary('path')
 
2364
        self.assertEqual(4, len(summary))
 
2365
        self.assertEqual('tree-reference', summary[0])
 
2366
 
 
2367
    def test_annotate(self):
 
2368
        tree = self.make_branch_and_tree('tree')
 
2369
        self.build_tree_contents([('tree/file', 'a\n')])
 
2370
        tree.add('file', 'file-id')
 
2371
        tree.commit('a', rev_id='one')
 
2372
        self.build_tree_contents([('tree/file', 'a\nb\n')])
 
2373
        preview = TransformPreview(tree)
 
2374
        self.addCleanup(preview.finalize)
 
2375
        file_trans_id = preview.trans_id_file_id('file-id')
 
2376
        preview.delete_contents(file_trans_id)
 
2377
        preview.create_file('a\nb\nc\n', file_trans_id)
 
2378
        preview_tree = preview.get_preview_tree()
 
2379
        expected = [
 
2380
            ('one', 'a\n'),
 
2381
            ('me:', 'b\n'),
 
2382
            ('me:', 'c\n'),
 
2383
        ]
 
2384
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2385
        self.assertEqual(expected, annotation)
 
2386
 
 
2387
    def test_annotate_missing(self):
 
2388
        preview = self.get_empty_preview()
 
2389
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2390
        preview_tree = preview.get_preview_tree()
 
2391
        expected = [
 
2392
            ('me:', 'a\n'),
 
2393
            ('me:', 'b\n'),
 
2394
            ('me:', 'c\n'),
 
2395
         ]
 
2396
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2397
        self.assertEqual(expected, annotation)
 
2398
 
 
2399
    def test_annotate_rename(self):
 
2400
        tree = self.make_branch_and_tree('tree')
 
2401
        self.build_tree_contents([('tree/file', 'a\n')])
 
2402
        tree.add('file', 'file-id')
 
2403
        tree.commit('a', rev_id='one')
 
2404
        preview = TransformPreview(tree)
 
2405
        self.addCleanup(preview.finalize)
 
2406
        file_trans_id = preview.trans_id_file_id('file-id')
 
2407
        preview.adjust_path('newname', preview.root, file_trans_id)
 
2408
        preview_tree = preview.get_preview_tree()
 
2409
        expected = [
 
2410
            ('one', 'a\n'),
 
2411
        ]
 
2412
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2413
        self.assertEqual(expected, annotation)
 
2414
 
 
2415
    def test_annotate_deleted(self):
 
2416
        tree = self.make_branch_and_tree('tree')
 
2417
        self.build_tree_contents([('tree/file', 'a\n')])
 
2418
        tree.add('file', 'file-id')
 
2419
        tree.commit('a', rev_id='one')
 
2420
        self.build_tree_contents([('tree/file', 'a\nb\n')])
 
2421
        preview = TransformPreview(tree)
 
2422
        self.addCleanup(preview.finalize)
 
2423
        file_trans_id = preview.trans_id_file_id('file-id')
 
2424
        preview.delete_contents(file_trans_id)
 
2425
        preview_tree = preview.get_preview_tree()
 
2426
        annotation = preview_tree.annotate_iter('file-id', 'me:')
 
2427
        self.assertIs(None, annotation)
 
2428
 
 
2429
    def test_stored_kind(self):
 
2430
        preview = self.get_empty_preview()
 
2431
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2432
        preview_tree = preview.get_preview_tree()
 
2433
        self.assertEqual('file', preview_tree.stored_kind('file-id'))
 
2434
 
 
2435
    def test_is_executable(self):
 
2436
        preview = self.get_empty_preview()
 
2437
        preview.new_file('file', preview.root, 'a\nb\nc\n', 'file-id')
 
2438
        preview.set_executability(True, preview.trans_id_file_id('file-id'))
 
2439
        preview_tree = preview.get_preview_tree()
 
2440
        self.assertEqual(True, preview_tree.is_executable('file-id'))
 
2441
 
 
2442
    def test_get_set_parent_ids(self):
 
2443
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
2444
        self.assertEqual([], preview_tree.get_parent_ids())
 
2445
        preview_tree.set_parent_ids(['rev-1'])
 
2446
        self.assertEqual(['rev-1'], preview_tree.get_parent_ids())
 
2447
 
 
2448
    def test_plan_file_merge(self):
 
2449
        work_a = self.make_branch_and_tree('wta')
 
2450
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
2451
        work_a.add('file', 'file-id')
 
2452
        base_id = work_a.commit('base version')
 
2453
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
2454
        preview = TransformPreview(work_a)
 
2455
        self.addCleanup(preview.finalize)
 
2456
        trans_id = preview.trans_id_file_id('file-id')
 
2457
        preview.delete_contents(trans_id)
 
2458
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
2459
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
2460
        tree_a = preview.get_preview_tree()
 
2461
        tree_a.set_parent_ids([base_id])
 
2462
        self.assertEqual([
 
2463
            ('killed-a', 'a\n'),
 
2464
            ('killed-b', 'b\n'),
 
2465
            ('unchanged', 'c\n'),
 
2466
            ('unchanged', 'd\n'),
 
2467
            ('new-a', 'e\n'),
 
2468
            ('new-b', 'f\n'),
 
2469
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
2470
 
 
2471
    def test_plan_file_merge_revision_tree(self):
 
2472
        work_a = self.make_branch_and_tree('wta')
 
2473
        self.build_tree_contents([('wta/file', 'a\nb\nc\nd\n')])
 
2474
        work_a.add('file', 'file-id')
 
2475
        base_id = work_a.commit('base version')
 
2476
        tree_b = work_a.bzrdir.sprout('wtb').open_workingtree()
 
2477
        preview = TransformPreview(work_a.basis_tree())
 
2478
        self.addCleanup(preview.finalize)
 
2479
        trans_id = preview.trans_id_file_id('file-id')
 
2480
        preview.delete_contents(trans_id)
 
2481
        preview.create_file('b\nc\nd\ne\n', trans_id)
 
2482
        self.build_tree_contents([('wtb/file', 'a\nc\nd\nf\n')])
 
2483
        tree_a = preview.get_preview_tree()
 
2484
        tree_a.set_parent_ids([base_id])
 
2485
        self.assertEqual([
 
2486
            ('killed-a', 'a\n'),
 
2487
            ('killed-b', 'b\n'),
 
2488
            ('unchanged', 'c\n'),
 
2489
            ('unchanged', 'd\n'),
 
2490
            ('new-a', 'e\n'),
 
2491
            ('new-b', 'f\n'),
 
2492
        ], list(tree_a.plan_file_merge('file-id', tree_b)))
 
2493
 
 
2494
    def test_walkdirs(self):
 
2495
        preview = self.get_empty_preview()
 
2496
        preview.version_file('tree-root', preview.root)
 
2497
        preview_tree = preview.get_preview_tree()
 
2498
        file_trans_id = preview.new_file('a', preview.root, 'contents',
 
2499
                                         'a-id')
 
2500
        expected = [(('', 'tree-root'),
 
2501
                    [('a', 'a', 'file', None, 'a-id', 'file')])]
 
2502
        self.assertEqual(expected, list(preview_tree.walkdirs()))
 
2503
 
 
2504
    def test_extras(self):
 
2505
        work_tree = self.make_branch_and_tree('tree')
 
2506
        self.build_tree(['tree/removed-file', 'tree/existing-file',
 
2507
                         'tree/not-removed-file'])
 
2508
        work_tree.add(['removed-file', 'not-removed-file'])
 
2509
        preview = TransformPreview(work_tree)
 
2510
        self.addCleanup(preview.finalize)
 
2511
        preview.new_file('new-file', preview.root, 'contents')
 
2512
        preview.new_file('new-versioned-file', preview.root, 'contents',
 
2513
                         'new-versioned-id')
 
2514
        tree = preview.get_preview_tree()
 
2515
        preview.unversion_file(preview.trans_id_tree_path('removed-file'))
 
2516
        self.assertEqual(set(['new-file', 'removed-file', 'existing-file']),
 
2517
                         set(tree.extras()))
 
2518
 
 
2519
    def test_merge_into_preview(self):
 
2520
        work_tree = self.make_branch_and_tree('tree')
 
2521
        self.build_tree_contents([('tree/file','b\n')])
 
2522
        work_tree.add('file', 'file-id')
 
2523
        work_tree.commit('first commit')
 
2524
        child_tree = work_tree.bzrdir.sprout('child').open_workingtree()
 
2525
        self.build_tree_contents([('child/file','b\nc\n')])
 
2526
        child_tree.commit('child commit')
 
2527
        child_tree.lock_write()
 
2528
        self.addCleanup(child_tree.unlock)
 
2529
        work_tree.lock_write()
 
2530
        self.addCleanup(work_tree.unlock)
 
2531
        preview = TransformPreview(work_tree)
 
2532
        self.addCleanup(preview.finalize)
 
2533
        preview_tree = preview.get_preview_tree()
 
2534
        file_trans_id = preview.trans_id_file_id('file-id')
 
2535
        preview.delete_contents(file_trans_id)
 
2536
        preview.create_file('a\nb\n', file_trans_id)
 
2537
        pb = progress.DummyProgress()
 
2538
        merger = Merger.from_revision_ids(pb, preview_tree,
 
2539
                                          child_tree.branch.last_revision(),
 
2540
                                          other_branch=child_tree.branch,
 
2541
                                          tree_branch=work_tree.branch)
 
2542
        merger.merge_type = Merge3Merger
 
2543
        tt = merger.make_merger().make_preview_transform()
 
2544
        self.addCleanup(tt.finalize)
 
2545
        final_tree = tt.get_preview_tree()
 
2546
        self.assertEqual('a\nb\nc\n', final_tree.get_file_text('file-id'))
 
2547
 
 
2548
    def test_merge_preview_into_workingtree(self):
 
2549
        tree = self.make_branch_and_tree('tree')
 
2550
        tt = TransformPreview(tree)
 
2551
        self.addCleanup(tt.finalize)
 
2552
        tt.new_file('name', tt.root, 'content', 'file-id')
 
2553
        tree2 = self.make_branch_and_tree('tree2')
 
2554
        pb = progress.DummyProgress()
 
2555
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2556
                                         pb, tree.basis_tree())
 
2557
        merger.merge_type = Merge3Merger
 
2558
        merger.do_merge()
 
2559
 
 
2560
    def test_merge_preview_into_workingtree_handles_conflicts(self):
 
2561
        tree = self.make_branch_and_tree('tree')
 
2562
        self.build_tree_contents([('tree/foo', 'bar')])
 
2563
        tree.add('foo', 'foo-id')
 
2564
        tree.commit('foo')
 
2565
        tt = TransformPreview(tree)
 
2566
        self.addCleanup(tt.finalize)
 
2567
        trans_id = tt.trans_id_file_id('foo-id')
 
2568
        tt.delete_contents(trans_id)
 
2569
        tt.create_file('baz', trans_id)
 
2570
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
 
2571
        self.build_tree_contents([('tree2/foo', 'qux')])
 
2572
        pb = progress.DummyProgress()
 
2573
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
 
2574
                                         pb, tree.basis_tree())
 
2575
        merger.merge_type = Merge3Merger
 
2576
        merger.do_merge()
 
2577
 
 
2578
    def test_is_executable(self):
 
2579
        tree = self.make_branch_and_tree('tree')
 
2580
        preview = TransformPreview(tree)
 
2581
        self.addCleanup(preview.finalize)
 
2582
        preview.new_file('foo', preview.root, 'bar', 'baz-id')
 
2583
        preview_tree = preview.get_preview_tree()
 
2584
        self.assertEqual(False, preview_tree.is_executable('baz-id',
 
2585
                                                           'tree/foo'))
 
2586
        self.assertEqual(False, preview_tree.is_executable('baz-id'))
 
2587
 
 
2588
 
 
2589
class FakeSerializer(object):
 
2590
    """Serializer implementation that simply returns the input.
 
2591
 
 
2592
    The input is returned in the order used by pack.ContainerPushParser.
 
2593
    """
 
2594
    @staticmethod
 
2595
    def bytes_record(bytes, names):
 
2596
        return names, bytes
 
2597
 
 
2598
 
 
2599
class TestSerializeTransform(tests.TestCaseWithTransport):
 
2600
 
 
2601
    _test_needs_features = [tests.UnicodeFilenameFeature]
 
2602
 
 
2603
    def get_preview(self, tree=None):
 
2604
        if tree is None:
 
2605
            tree = self.make_branch_and_tree('tree')
 
2606
        tt = TransformPreview(tree)
 
2607
        self.addCleanup(tt.finalize)
 
2608
        return tt
 
2609
 
 
2610
    def assertSerializesTo(self, expected, tt):
 
2611
        records = list(tt.serialize(FakeSerializer()))
 
2612
        self.assertEqual(expected, records)
 
2613
 
 
2614
    @staticmethod
 
2615
    def default_attribs():
 
2616
        return {
 
2617
            '_id_number': 1,
 
2618
            '_new_name': {},
 
2619
            '_new_parent': {},
 
2620
            '_new_executability': {},
 
2621
            '_new_id': {},
 
2622
            '_tree_path_ids': {'': 'new-0'},
 
2623
            '_removed_id': [],
 
2624
            '_removed_contents': [],
 
2625
            '_non_present_ids': {},
 
2626
            }
 
2627
 
 
2628
    def make_records(self, attribs, contents):
 
2629
        records = [
 
2630
            (((('attribs'),),), bencode.bencode(attribs))]
 
2631
        records.extend([(((n, k),), c) for n, k, c in contents])
 
2632
        return records
 
2633
 
 
2634
    def creation_records(self):
 
2635
        attribs = self.default_attribs()
 
2636
        attribs['_id_number'] = 3
 
2637
        attribs['_new_name'] = {
 
2638
            'new-1': u'foo\u1234'.encode('utf-8'), 'new-2': 'qux'}
 
2639
        attribs['_new_id'] = {'new-1': 'baz', 'new-2': 'quxx'}
 
2640
        attribs['_new_parent'] = {'new-1': 'new-0', 'new-2': 'new-0'}
 
2641
        attribs['_new_executability'] = {'new-1': 1}
 
2642
        contents = [
 
2643
            ('new-1', 'file', 'i 1\nbar\n'),
 
2644
            ('new-2', 'directory', ''),
 
2645
            ]
 
2646
        return self.make_records(attribs, contents)
 
2647
 
 
2648
    def test_serialize_creation(self):
 
2649
        tt = self.get_preview()
 
2650
        tt.new_file(u'foo\u1234', tt.root, 'bar', 'baz', True)
 
2651
        tt.new_directory('qux', tt.root, 'quxx')
 
2652
        self.assertSerializesTo(self.creation_records(), tt)
 
2653
 
 
2654
    def test_deserialize_creation(self):
 
2655
        tt = self.get_preview()
 
2656
        tt.deserialize(iter(self.creation_records()))
 
2657
        self.assertEqual(3, tt._id_number)
 
2658
        self.assertEqual({'new-1': u'foo\u1234',
 
2659
                          'new-2': 'qux'}, tt._new_name)
 
2660
        self.assertEqual({'new-1': 'baz', 'new-2': 'quxx'}, tt._new_id)
 
2661
        self.assertEqual({'new-1': tt.root, 'new-2': tt.root}, tt._new_parent)
 
2662
        self.assertEqual({'baz': 'new-1', 'quxx': 'new-2'}, tt._r_new_id)
 
2663
        self.assertEqual({'new-1': True}, tt._new_executability)
 
2664
        self.assertEqual({'new-1': 'file',
 
2665
                          'new-2': 'directory'}, tt._new_contents)
 
2666
        foo_limbo = open(tt._limbo_name('new-1'), 'rb')
 
2667
        try:
 
2668
            foo_content = foo_limbo.read()
 
2669
        finally:
 
2670
            foo_limbo.close()
 
2671
        self.assertEqual('bar', foo_content)
 
2672
 
 
2673
    def symlink_creation_records(self):
 
2674
        attribs = self.default_attribs()
 
2675
        attribs['_id_number'] = 2
 
2676
        attribs['_new_name'] = {'new-1': u'foo\u1234'.encode('utf-8')}
 
2677
        attribs['_new_parent'] = {'new-1': 'new-0'}
 
2678
        contents = [('new-1', 'symlink', u'bar\u1234'.encode('utf-8'))]
 
2679
        return self.make_records(attribs, contents)
 
2680
 
 
2681
    def test_serialize_symlink_creation(self):
 
2682
        self.requireFeature(tests.SymlinkFeature)
 
2683
        tt = self.get_preview()
 
2684
        tt.new_symlink(u'foo\u1234', tt.root, u'bar\u1234')
 
2685
        self.assertSerializesTo(self.symlink_creation_records(), tt)
 
2686
 
 
2687
    def test_deserialize_symlink_creation(self):
 
2688
        tt = self.get_preview()
 
2689
        tt.deserialize(iter(self.symlink_creation_records()))
 
2690
        # XXX readlink should be returning unicode, not utf-8
 
2691
        foo_content = os.readlink(tt._limbo_name('new-1')).decode('utf-8')
 
2692
        self.assertEqual(u'bar\u1234', foo_content)
 
2693
 
 
2694
    def make_destruction_preview(self):
 
2695
        tree = self.make_branch_and_tree('.')
 
2696
        self.build_tree([u'foo\u1234', 'bar'])
 
2697
        tree.add([u'foo\u1234', 'bar'], ['foo-id', 'bar-id'])
 
2698
        return self.get_preview(tree)
 
2699
 
 
2700
    def destruction_records(self):
 
2701
        attribs = self.default_attribs()
 
2702
        attribs['_id_number'] = 3
 
2703
        attribs['_removed_id'] = ['new-1']
 
2704
        attribs['_removed_contents'] = ['new-2']
 
2705
        attribs['_tree_path_ids'] = {
 
2706
            '': 'new-0',
 
2707
            u'foo\u1234'.encode('utf-8'): 'new-1',
 
2708
            'bar': 'new-2',
 
2709
            }
 
2710
        return self.make_records(attribs, [])
 
2711
 
 
2712
    def test_serialize_destruction(self):
 
2713
        tt = self.make_destruction_preview()
 
2714
        foo_trans_id = tt.trans_id_tree_file_id('foo-id')
 
2715
        tt.unversion_file(foo_trans_id)
 
2716
        bar_trans_id = tt.trans_id_tree_file_id('bar-id')
 
2717
        tt.delete_contents(bar_trans_id)
 
2718
        self.assertSerializesTo(self.destruction_records(), tt)
 
2719
 
 
2720
    def test_deserialize_destruction(self):
 
2721
        tt = self.make_destruction_preview()
 
2722
        tt.deserialize(iter(self.destruction_records()))
 
2723
        self.assertEqual({u'foo\u1234': 'new-1',
 
2724
                          'bar': 'new-2',
 
2725
                          '': tt.root}, tt._tree_path_ids)
 
2726
        self.assertEqual({'new-1': u'foo\u1234',
 
2727
                          'new-2': 'bar',
 
2728
                          tt.root: ''}, tt._tree_id_paths)
 
2729
        self.assertEqual(set(['new-1']), tt._removed_id)
 
2730
        self.assertEqual(set(['new-2']), tt._removed_contents)
 
2731
 
 
2732
    def missing_records(self):
 
2733
        attribs = self.default_attribs()
 
2734
        attribs['_id_number'] = 2
 
2735
        attribs['_non_present_ids'] = {
 
2736
            'boo': 'new-1',}
 
2737
        return self.make_records(attribs, [])
 
2738
 
 
2739
    def test_serialize_missing(self):
 
2740
        tt = self.get_preview()
 
2741
        boo_trans_id = tt.trans_id_file_id('boo')
 
2742
        self.assertSerializesTo(self.missing_records(), tt)
 
2743
 
 
2744
    def test_deserialize_missing(self):
 
2745
        tt = self.get_preview()
 
2746
        tt.deserialize(iter(self.missing_records()))
 
2747
        self.assertEqual({'boo': 'new-1'}, tt._non_present_ids)
 
2748
 
 
2749
    def make_modification_preview(self):
 
2750
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
2751
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
2752
        tree = self.make_branch_and_tree('tree')
 
2753
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
2754
        tree.add('file', 'file-id')
 
2755
        return self.get_preview(tree), LINES_TWO
 
2756
 
 
2757
    def modification_records(self):
 
2758
        attribs = self.default_attribs()
 
2759
        attribs['_id_number'] = 2
 
2760
        attribs['_tree_path_ids'] = {
 
2761
            'file': 'new-1',
 
2762
            '': 'new-0',}
 
2763
        attribs['_removed_contents'] = ['new-1']
 
2764
        contents = [('new-1', 'file',
 
2765
                     'i 1\nz\n\nc 0 1 1 1\ni 1\nx\n\nc 0 3 3 1\n')]
 
2766
        return self.make_records(attribs, contents)
 
2767
 
 
2768
    def test_serialize_modification(self):
 
2769
        tt, LINES = self.make_modification_preview()
 
2770
        trans_id = tt.trans_id_file_id('file-id')
 
2771
        tt.delete_contents(trans_id)
 
2772
        tt.create_file(LINES, trans_id)
 
2773
        self.assertSerializesTo(self.modification_records(), tt)
 
2774
 
 
2775
    def test_deserialize_modification(self):
 
2776
        tt, LINES = self.make_modification_preview()
 
2777
        tt.deserialize(iter(self.modification_records()))
 
2778
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
2779
 
 
2780
    def make_kind_change_preview(self):
 
2781
        LINES = 'a\nb\nc\nd\n'
 
2782
        tree = self.make_branch_and_tree('tree')
 
2783
        self.build_tree(['tree/foo/'])
 
2784
        tree.add('foo', 'foo-id')
 
2785
        return self.get_preview(tree), LINES
 
2786
 
 
2787
    def kind_change_records(self):
 
2788
        attribs = self.default_attribs()
 
2789
        attribs['_id_number'] = 2
 
2790
        attribs['_tree_path_ids'] = {
 
2791
            'foo': 'new-1',
 
2792
            '': 'new-0',}
 
2793
        attribs['_removed_contents'] = ['new-1']
 
2794
        contents = [('new-1', 'file',
 
2795
                     'i 4\na\nb\nc\nd\n\n')]
 
2796
        return self.make_records(attribs, contents)
 
2797
 
 
2798
    def test_serialize_kind_change(self):
 
2799
        tt, LINES = self.make_kind_change_preview()
 
2800
        trans_id = tt.trans_id_file_id('foo-id')
 
2801
        tt.delete_contents(trans_id)
 
2802
        tt.create_file(LINES, trans_id)
 
2803
        self.assertSerializesTo(self.kind_change_records(), tt)
 
2804
 
 
2805
    def test_deserialize_kind_change(self):
 
2806
        tt, LINES = self.make_kind_change_preview()
 
2807
        tt.deserialize(iter(self.kind_change_records()))
 
2808
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
2809
 
 
2810
    def make_add_contents_preview(self):
 
2811
        LINES = 'a\nb\nc\nd\n'
 
2812
        tree = self.make_branch_and_tree('tree')
 
2813
        self.build_tree(['tree/foo'])
 
2814
        tree.add('foo')
 
2815
        os.unlink('tree/foo')
 
2816
        return self.get_preview(tree), LINES
 
2817
 
 
2818
    def add_contents_records(self):
 
2819
        attribs = self.default_attribs()
 
2820
        attribs['_id_number'] = 2
 
2821
        attribs['_tree_path_ids'] = {
 
2822
            'foo': 'new-1',
 
2823
            '': 'new-0',}
 
2824
        contents = [('new-1', 'file',
 
2825
                     'i 4\na\nb\nc\nd\n\n')]
 
2826
        return self.make_records(attribs, contents)
 
2827
 
 
2828
    def test_serialize_add_contents(self):
 
2829
        tt, LINES = self.make_add_contents_preview()
 
2830
        trans_id = tt.trans_id_tree_path('foo')
 
2831
        tt.create_file(LINES, trans_id)
 
2832
        self.assertSerializesTo(self.add_contents_records(), tt)
 
2833
 
 
2834
    def test_deserialize_add_contents(self):
 
2835
        tt, LINES = self.make_add_contents_preview()
 
2836
        tt.deserialize(iter(self.add_contents_records()))
 
2837
        self.assertFileEqual(LINES, tt._limbo_name('new-1'))
 
2838
 
 
2839
    def test_get_parents_lines(self):
 
2840
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
2841
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
2842
        tree = self.make_branch_and_tree('tree')
 
2843
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
2844
        tree.add('file', 'file-id')
 
2845
        tt = self.get_preview(tree)
 
2846
        trans_id = tt.trans_id_tree_path('file')
 
2847
        self.assertEqual((['aa\n', 'bb\n', 'cc\n', 'dd\n'],),
 
2848
            tt._get_parents_lines(trans_id))
 
2849
 
 
2850
    def test_get_parents_texts(self):
 
2851
        LINES_ONE = 'aa\nbb\ncc\ndd\n'
 
2852
        LINES_TWO = 'z\nbb\nx\ndd\n'
 
2853
        tree = self.make_branch_and_tree('tree')
 
2854
        self.build_tree_contents([('tree/file', LINES_ONE)])
 
2855
        tree.add('file', 'file-id')
 
2856
        tt = self.get_preview(tree)
 
2857
        trans_id = tt.trans_id_tree_path('file')
 
2858
        self.assertEqual((LINES_ONE,),
 
2859
            tt._get_parents_texts(trans_id))