/brz/remove-bazaar

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

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Andrew Bennetts
  • Date: 2008-01-24 03:21:43 UTC
  • mto: This revision was merged to the branch mainline in revision 3200.
  • Revision ID: andrew.bennetts@canonical.com-20080124032143-srxbec60xf9dt3xy
Just use urllib.unquote, as suggested by John's review.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
# Copyright (C) 2006 Canonical Ltd
2
 
 
 
2
#
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
5
5
# the Free Software Foundation; either version 2 of the License, or
6
6
# (at your option) any later version.
7
 
 
 
7
#
8
8
# This program is distributed in the hope that it will be useful,
9
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
11
# GNU General Public License for more details.
12
 
 
 
12
#
13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
16
 
17
17
import os
 
18
import stat
 
19
from StringIO import StringIO
 
20
import sys
18
21
 
 
22
from bzrlib import (
 
23
    errors,
 
24
    generate_ids,
 
25
    progress,
 
26
    revision as _mod_revision,
 
27
    symbol_versioning,
 
28
    tests,
 
29
    urlutils,
 
30
    )
19
31
from bzrlib.bzrdir import BzrDir
20
32
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
21
 
                              UnversionedParent, ParentLoop)
 
33
                              UnversionedParent, ParentLoop, DeletingParent,
 
34
                              NonDirectoryParent)
 
35
from bzrlib.diff import show_diff_trees
22
36
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
23
 
                           ReusingTransform, CantMoveRoot, NotVersionedError,
24
 
                           ExistingLimbo, ImmortalLimbo, LockError)
25
 
from bzrlib.osutils import file_kind, has_symlinks, pathjoin
 
37
                           ReusingTransform, CantMoveRoot, 
 
38
                           PathsNotVersionedError, ExistingLimbo,
 
39
                           ExistingPendingDeletion, ImmortalLimbo,
 
40
                           ImmortalPendingDeletion, LockError)
 
41
from bzrlib.osutils import file_kind, pathjoin
26
42
from bzrlib.merge import Merge3Merger
27
 
from bzrlib.tests import TestCaseInTempDir, TestSkipped, TestCase
 
43
from bzrlib.tests import (
 
44
    CaseInsensitiveFilesystemFeature,
 
45
    SymlinkFeature,
 
46
    TestCase,
 
47
    TestCaseInTempDir,
 
48
    TestSkipped,
 
49
    )
28
50
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths, 
29
51
                              resolve_conflicts, cook_conflicts, 
30
 
                              find_interesting, build_tree, get_backup_name)
31
 
 
32
 
class TestTreeTransform(TestCaseInTempDir):
 
52
                              find_interesting, build_tree, get_backup_name,
 
53
                              change_entry, _FileMover, resolve_checkout,
 
54
                              TransformPreview)
 
55
 
 
56
class TestTreeTransform(tests.TestCaseWithTransport):
 
57
 
33
58
    def setUp(self):
34
59
        super(TestTreeTransform, self).setUp()
35
 
        self.wt = BzrDir.create_standalone_workingtree('.')
 
60
        self.wt = self.make_branch_and_tree('.', format='dirstate-with-subtree')
36
61
        os.chdir('..')
37
62
 
38
63
    def get_transform(self):
39
64
        transform = TreeTransform(self.wt)
40
65
        #self.addCleanup(transform.finalize)
41
 
        return transform, transform.trans_id_tree_file_id(self.wt.get_root_id())
 
66
        return transform, transform.root
42
67
 
43
68
    def test_existing_limbo(self):
44
 
        limbo_name = self.wt._control_files.controlfilename('limbo')
45
69
        transform, root = self.get_transform()
 
70
        limbo_name = transform._limbodir
 
71
        deletion_path = transform._deletiondir
46
72
        os.mkdir(pathjoin(limbo_name, 'hehe'))
47
73
        self.assertRaises(ImmortalLimbo, transform.apply)
48
74
        self.assertRaises(LockError, self.wt.unlock)
50
76
        self.assertRaises(LockError, self.wt.unlock)
51
77
        os.rmdir(pathjoin(limbo_name, 'hehe'))
52
78
        os.rmdir(limbo_name)
 
79
        os.rmdir(deletion_path)
53
80
        transform, root = self.get_transform()
54
81
        transform.apply()
55
82
 
 
83
    def test_existing_pending_deletion(self):
 
84
        transform, root = self.get_transform()
 
85
        deletion_path = self._limbodir = urlutils.local_path_from_url(
 
86
            transform._tree._control_files.controlfilename('pending-deletion'))
 
87
        os.mkdir(pathjoin(deletion_path, 'blocking-directory'))
 
88
        self.assertRaises(ImmortalPendingDeletion, transform.apply)
 
89
        self.assertRaises(LockError, self.wt.unlock)
 
90
        self.assertRaises(ExistingPendingDeletion, self.get_transform)
 
91
 
56
92
    def test_build(self):
57
 
        transform, root = self.get_transform() 
 
93
        transform, root = self.get_transform()
 
94
        self.wt.lock_tree_write()
 
95
        self.addCleanup(self.wt.unlock)
58
96
        self.assertIs(transform.get_tree_parent(root), ROOT_PARENT)
59
97
        imaginary_id = transform.trans_id_tree_path('imaginary')
 
98
        imaginary_id2 = transform.trans_id_tree_path('imaginary/')
 
99
        self.assertEqual(imaginary_id, imaginary_id2)
60
100
        self.assertEqual(transform.get_tree_parent(imaginary_id), root)
61
101
        self.assertEqual(transform.final_kind(root), 'directory')
62
102
        self.assertEqual(transform.final_file_id(root), self.wt.get_root_id())
95
135
 
96
136
    def test_convenience(self):
97
137
        transform, root = self.get_transform()
 
138
        self.wt.lock_tree_write()
 
139
        self.addCleanup(self.wt.unlock)
98
140
        trans_id = transform.new_file('name', root, 'contents', 
99
141
                                      'my_pretties', True)
100
142
        oz = transform.new_directory('oz', root, 'oz-id')
112
154
        self.assertEqual(self.wt.path2id('oz/dorothy'), 'dorothy-id')
113
155
        self.assertEqual(self.wt.path2id('oz/dorothy/toto'), 'toto-id')
114
156
 
115
 
        self.assertEqual('toto-contents', 
 
157
        self.assertEqual('toto-contents',
116
158
                         self.wt.get_file_byname('oz/dorothy/toto').read())
117
159
        self.assertIs(self.wt.is_executable('toto-id'), False)
118
160
 
 
161
    def test_tree_reference(self):
 
162
        transform, root = self.get_transform()
 
163
        tree = transform._tree
 
164
        trans_id = transform.new_directory('reference', root, 'subtree-id')
 
165
        transform.set_tree_reference('subtree-revision', trans_id)
 
166
        transform.apply()
 
167
        tree.lock_read()
 
168
        self.addCleanup(tree.unlock)
 
169
        self.assertEqual('subtree-revision',
 
170
                         tree.inventory['subtree-id'].reference_revision)
 
171
 
119
172
    def test_conflicts(self):
120
173
        transform, root = self.get_transform()
121
174
        trans_id = transform.new_file('name', root, 'contents', 
185
238
        transform3.delete_contents(oz_id)
186
239
        self.assertEqual(transform3.find_conflicts(), 
187
240
                         [('missing parent', oz_id)])
188
 
        root_id = transform3.trans_id_tree_file_id('TREE_ROOT')
 
241
        root_id = transform3.root
189
242
        tip_id = transform3.trans_id_tree_file_id('tip-id')
190
243
        transform3.adjust_path('tip', root_id, tip_id)
191
244
        transform3.apply()
192
245
 
 
246
    def test_conflict_on_case_insensitive(self):
 
247
        tree = self.make_branch_and_tree('tree')
 
248
        # Don't try this at home, kids!
 
249
        # Force the tree to report that it is case sensitive, for conflict
 
250
        # resolution tests
 
251
        tree.case_sensitive = True
 
252
        transform = TreeTransform(tree)
 
253
        self.addCleanup(transform.finalize)
 
254
        transform.new_file('file', transform.root, 'content')
 
255
        transform.new_file('FiLe', transform.root, 'content')
 
256
        result = transform.find_conflicts()
 
257
        self.assertEqual([], result)
 
258
        transform.finalize()
 
259
        # Force the tree to report that it is case insensitive, for conflict
 
260
        # generation tests
 
261
        tree.case_sensitive = False
 
262
        transform = TreeTransform(tree)
 
263
        self.addCleanup(transform.finalize)
 
264
        transform.new_file('file', transform.root, 'content')
 
265
        transform.new_file('FiLe', transform.root, 'content')
 
266
        result = transform.find_conflicts()
 
267
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
 
268
 
 
269
    def test_conflict_on_case_insensitive_existing(self):
 
270
        tree = self.make_branch_and_tree('tree')
 
271
        self.build_tree(['tree/FiLe'])
 
272
        # Don't try this at home, kids!
 
273
        # Force the tree to report that it is case sensitive, for conflict
 
274
        # resolution tests
 
275
        tree.case_sensitive = True
 
276
        transform = TreeTransform(tree)
 
277
        self.addCleanup(transform.finalize)
 
278
        transform.new_file('file', transform.root, 'content')
 
279
        result = transform.find_conflicts()
 
280
        self.assertEqual([], result)
 
281
        transform.finalize()
 
282
        # Force the tree to report that it is case insensitive, for conflict
 
283
        # generation tests
 
284
        tree.case_sensitive = False
 
285
        transform = TreeTransform(tree)
 
286
        self.addCleanup(transform.finalize)
 
287
        transform.new_file('file', transform.root, 'content')
 
288
        result = transform.find_conflicts()
 
289
        self.assertEqual([('duplicate', 'new-1', 'new-2', 'file')], result)
 
290
 
 
291
    def test_resolve_case_insensitive_conflict(self):
 
292
        tree = self.make_branch_and_tree('tree')
 
293
        # Don't try this at home, kids!
 
294
        # Force the tree to report that it is case insensitive, for conflict
 
295
        # resolution tests
 
296
        tree.case_sensitive = False
 
297
        transform = TreeTransform(tree)
 
298
        self.addCleanup(transform.finalize)
 
299
        transform.new_file('file', transform.root, 'content')
 
300
        transform.new_file('FiLe', transform.root, 'content')
 
301
        resolve_conflicts(transform)
 
302
        transform.apply()
 
303
        self.failUnlessExists('tree/file')
 
304
        self.failUnlessExists('tree/FiLe.moved')
 
305
 
 
306
    def test_resolve_checkout_case_conflict(self):
 
307
        tree = self.make_branch_and_tree('tree')
 
308
        # Don't try this at home, kids!
 
309
        # Force the tree to report that it is case insensitive, for conflict
 
310
        # resolution tests
 
311
        tree.case_sensitive = False
 
312
        transform = TreeTransform(tree)
 
313
        self.addCleanup(transform.finalize)
 
314
        transform.new_file('file', transform.root, 'content')
 
315
        transform.new_file('FiLe', transform.root, 'content')
 
316
        resolve_conflicts(transform,
 
317
                          pass_func=lambda t, c: resolve_checkout(t, c, []))
 
318
        transform.apply()
 
319
        self.failUnlessExists('tree/file')
 
320
        self.failUnlessExists('tree/FiLe.moved')
 
321
 
 
322
    def test_apply_case_conflict(self):
 
323
        """Ensure that a transform with case conflicts can always be applied"""
 
324
        tree = self.make_branch_and_tree('tree')
 
325
        transform = TreeTransform(tree)
 
326
        self.addCleanup(transform.finalize)
 
327
        transform.new_file('file', transform.root, 'content')
 
328
        transform.new_file('FiLe', transform.root, 'content')
 
329
        dir = transform.new_directory('dir', transform.root)
 
330
        transform.new_file('dirfile', dir, 'content')
 
331
        transform.new_file('dirFiLe', dir, 'content')
 
332
        resolve_conflicts(transform)
 
333
        transform.apply()
 
334
        self.failUnlessExists('tree/file')
 
335
        if not os.path.exists('tree/FiLe.moved'):
 
336
            self.failUnlessExists('tree/FiLe')
 
337
        self.failUnlessExists('tree/dir/dirfile')
 
338
        if not os.path.exists('tree/dir/dirFiLe.moved'):
 
339
            self.failUnlessExists('tree/dir/dirFiLe')
 
340
 
 
341
    def test_case_insensitive_limbo(self):
 
342
        tree = self.make_branch_and_tree('tree')
 
343
        # Don't try this at home, kids!
 
344
        # Force the tree to report that it is case insensitive
 
345
        tree.case_sensitive = False
 
346
        transform = TreeTransform(tree)
 
347
        self.addCleanup(transform.finalize)
 
348
        dir = transform.new_directory('dir', transform.root)
 
349
        first = transform.new_file('file', dir, 'content')
 
350
        second = transform.new_file('FiLe', dir, 'content')
 
351
        self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
 
352
        self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
 
353
 
193
354
    def test_add_del(self):
194
355
        start, root = self.get_transform()
195
356
        start.new_directory('a', root, 'a')
217
378
    def test_name_invariants(self):
218
379
        create_tree, root = self.get_transform()
219
380
        # prepare tree
220
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
381
        root = create_tree.root
221
382
        create_tree.new_file('name1', root, 'hello1', 'name1')
222
383
        create_tree.new_file('name2', root, 'hello2', 'name2')
223
384
        ddir = create_tree.new_directory('dying_directory', root, 'ddir')
227
388
        create_tree.apply()
228
389
 
229
390
        mangle_tree,root = self.get_transform()
230
 
        root = mangle_tree.trans_id_tree_file_id('TREE_ROOT')
 
391
        root = mangle_tree.root
231
392
        #swap names
232
393
        name1 = mangle_tree.trans_id_tree_file_id('name1')
233
394
        name2 = mangle_tree.trans_id_tree_file_id('name2')
312
473
    def test_move_dangling_ie(self):
313
474
        create_tree, root = self.get_transform()
314
475
        # prepare tree
315
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
476
        root = create_tree.root
316
477
        create_tree.new_file('name1', root, 'hello1', 'name1')
317
478
        create_tree.apply()
318
479
        delete_contents, root = self.get_transform()
328
489
    def test_replace_dangling_ie(self):
329
490
        create_tree, root = self.get_transform()
330
491
        # prepare tree
331
 
        root = create_tree.trans_id_tree_file_id('TREE_ROOT')
 
492
        root = create_tree.root
332
493
        create_tree.new_file('name1', root, 'hello1', 'name1')
333
494
        create_tree.apply()
334
495
        delete_contents = TreeTransform(self.wt)
347
508
        replace.apply()
348
509
 
349
510
    def test_symlinks(self):
350
 
        if not has_symlinks():
351
 
            raise TestSkipped('Symlinks are not supported on this platform')
 
511
        self.requireFeature(SymlinkFeature)
352
512
        transform,root = self.get_transform()
353
513
        oz_id = transform.new_directory('oz', root, 'oz-id')
354
514
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target', 
368
528
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
369
529
                         'wizard-target')
370
530
 
 
531
    def test_unable_create_symlink(self):
 
532
        def tt_helper():
 
533
            wt = self.make_branch_and_tree('.')
 
534
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
535
            try:
 
536
                tt.new_symlink('foo', tt.root, 'bar')
 
537
                tt.apply()
 
538
            finally:
 
539
                wt.unlock()
 
540
        os_symlink = getattr(os, 'symlink', None)
 
541
        os.symlink = None
 
542
        try:
 
543
            err = self.assertRaises(errors.UnableCreateSymlink, tt_helper)
 
544
            self.assertEquals(
 
545
                "Unable to create symlink 'foo' on this platform",
 
546
                str(err))
 
547
        finally:
 
548
            if os_symlink:
 
549
                os.symlink = os_symlink
371
550
 
372
551
    def get_conflicted(self):
373
552
        create,root = self.get_transform()
381
560
                                         'dorothy-id')
382
561
        old_dorothy = conflicts.trans_id_tree_file_id('dorothy-id')
383
562
        oz = conflicts.trans_id_tree_file_id('oz-id')
384
 
        # set up missing, unversioned parent
 
563
        # set up DeletedParent parent conflict
385
564
        conflicts.delete_versioned(oz)
386
565
        emerald = conflicts.trans_id_tree_file_id('emerald-id')
 
566
        # set up MissingParent conflict
 
567
        munchkincity = conflicts.trans_id_file_id('munchkincity-id')
 
568
        conflicts.adjust_path('munchkincity', root, munchkincity)
 
569
        conflicts.new_directory('auntem', munchkincity, 'auntem-id')
387
570
        # set up parent loop
388
571
        conflicts.adjust_path('emeraldcity', emerald, emerald)
389
572
        return conflicts, emerald, oz, old_dorothy, new_dorothy
410
593
                                   'dorothy.moved', 'dorothy', None,
411
594
                                   'dorothy-id')
412
595
        self.assertEqual(cooked_conflicts[1], duplicate_id)
413
 
        missing_parent = MissingParent('Not deleting', 'oz', 'oz-id')
 
596
        missing_parent = MissingParent('Created directory', 'munchkincity',
 
597
                                       'munchkincity-id')
 
598
        deleted_parent = DeletingParent('Not deleting', 'oz', 'oz-id')
414
599
        self.assertEqual(cooked_conflicts[2], missing_parent)
415
 
        unversioned_parent = UnversionedParent('Versioned directory', 'oz',
 
600
        unversioned_parent = UnversionedParent('Versioned directory',
 
601
                                               'munchkincity',
 
602
                                               'munchkincity-id')
 
603
        unversioned_parent2 = UnversionedParent('Versioned directory', 'oz',
416
604
                                               'oz-id')
417
605
        self.assertEqual(cooked_conflicts[3], unversioned_parent)
418
606
        parent_loop = ParentLoop('Cancelled move', 'oz/emeraldcity', 
419
607
                                 'oz/emeraldcity', 'emerald-id', 'emerald-id')
420
 
        self.assertEqual(cooked_conflicts[4], parent_loop)
421
 
        self.assertEqual(len(cooked_conflicts), 5)
 
608
        self.assertEqual(cooked_conflicts[4], deleted_parent)
 
609
        self.assertEqual(cooked_conflicts[5], unversioned_parent2)
 
610
        self.assertEqual(cooked_conflicts[6], parent_loop)
 
611
        self.assertEqual(len(cooked_conflicts), 7)
422
612
        tt.finalize()
423
613
 
424
614
    def test_string_conflicts(self):
434
624
        self.assertEqual(conflicts_s[1], 'Conflict adding id to dorothy.  '
435
625
                                         'Unversioned existing file '
436
626
                                         'dorothy.moved.')
437
 
        self.assertEqual(conflicts_s[2], 'Conflict adding files to oz.  '
438
 
                                         'Not deleting.')
439
 
        self.assertEqual(conflicts_s[3], 'Conflict adding versioned files to '
440
 
                                         'oz.  Versioned directory.')
441
 
        self.assertEqual(conflicts_s[4], 'Conflict moving oz/emeraldcity into'
 
627
        self.assertEqual(conflicts_s[2], 'Conflict adding files to'
 
628
                                         ' munchkincity.  Created directory.')
 
629
        self.assertEqual(conflicts_s[3], 'Conflict because munchkincity is not'
 
630
                                         ' versioned, but has versioned'
 
631
                                         ' children.  Versioned directory.')
 
632
        self.assertEqualDiff(conflicts_s[4], "Conflict: can't delete oz because it"
 
633
                                         " is not empty.  Not deleting.")
 
634
        self.assertEqual(conflicts_s[5], 'Conflict because oz is not'
 
635
                                         ' versioned, but has versioned'
 
636
                                         ' children.  Versioned directory.')
 
637
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
442
638
                                         ' oz/emeraldcity.  Cancelled move.')
443
639
 
 
640
    def prepare_wrong_parent_kind(self):
 
641
        tt, root = self.get_transform()
 
642
        tt.new_file('parent', root, 'contents', 'parent-id')
 
643
        tt.apply()
 
644
        tt, root = self.get_transform()
 
645
        parent_id = tt.trans_id_file_id('parent-id')
 
646
        tt.new_file('child,', parent_id, 'contents2', 'file-id')
 
647
        return tt
 
648
 
 
649
    def test_find_conflicts_wrong_parent_kind(self):
 
650
        tt = self.prepare_wrong_parent_kind()
 
651
        tt.find_conflicts()
 
652
 
 
653
    def test_resolve_conflicts_wrong_existing_parent_kind(self):
 
654
        tt = self.prepare_wrong_parent_kind()
 
655
        raw_conflicts = resolve_conflicts(tt)
 
656
        self.assertEqual(set([('non-directory parent', 'Created directory',
 
657
                         'new-3')]), raw_conflicts)
 
658
        cooked_conflicts = cook_conflicts(raw_conflicts, tt)
 
659
        self.assertEqual([NonDirectoryParent('Created directory', 'parent.new',
 
660
        'parent-id')], cooked_conflicts)
 
661
        tt.apply()
 
662
        self.assertEqual(None, self.wt.path2id('parent'))
 
663
        self.assertEqual('parent-id', self.wt.path2id('parent.new'))
 
664
 
 
665
    def test_resolve_conflicts_wrong_new_parent_kind(self):
 
666
        tt, root = self.get_transform()
 
667
        parent_id = tt.new_directory('parent', root, 'parent-id')
 
668
        tt.new_file('child,', parent_id, 'contents2', 'file-id')
 
669
        tt.apply()
 
670
        tt, root = self.get_transform()
 
671
        parent_id = tt.trans_id_file_id('parent-id')
 
672
        tt.delete_contents(parent_id)
 
673
        tt.create_file('contents', parent_id)
 
674
        raw_conflicts = resolve_conflicts(tt)
 
675
        self.assertEqual(set([('non-directory parent', 'Created directory',
 
676
                         'new-3')]), raw_conflicts)
 
677
        tt.apply()
 
678
        self.assertEqual(None, self.wt.path2id('parent'))
 
679
        self.assertEqual('parent-id', self.wt.path2id('parent.new'))
 
680
 
 
681
    def test_resolve_conflicts_wrong_parent_kind_unversioned(self):
 
682
        tt, root = self.get_transform()
 
683
        parent_id = tt.new_directory('parent', root)
 
684
        tt.new_file('child,', parent_id, 'contents2')
 
685
        tt.apply()
 
686
        tt, root = self.get_transform()
 
687
        parent_id = tt.trans_id_tree_path('parent')
 
688
        tt.delete_contents(parent_id)
 
689
        tt.create_file('contents', parent_id)
 
690
        resolve_conflicts(tt)
 
691
        tt.apply()
 
692
        self.assertIs(None, self.wt.path2id('parent'))
 
693
        self.assertIs(None, self.wt.path2id('parent.new'))
 
694
 
444
695
    def test_moving_versioned_directories(self):
445
696
        create, root = self.get_transform()
446
697
        kansas = create.new_directory('kansas', root, 'kansas-id')
485
736
        create.new_file('vfile', root, 'myfile-text', 'myfile-id')
486
737
        create.new_file('uvfile', root, 'othertext')
487
738
        create.apply()
488
 
        self.assertEqual(find_interesting(wt, wt, ['vfile']),
489
 
                         set(['myfile-id']))
490
 
        self.assertRaises(NotVersionedError, find_interesting, wt, wt,
491
 
                          ['uvfile'])
 
739
        result = self.applyDeprecated(symbol_versioning.zero_fifteen,
 
740
            find_interesting, wt, wt, ['vfile'])
 
741
        self.assertEqual(result, set(['myfile-id']))
 
742
 
 
743
    def test_set_executability_order(self):
 
744
        """Ensure that executability behaves the same, no matter what order.
 
745
        
 
746
        - create file and set executability simultaneously
 
747
        - create file and set executability afterward
 
748
        - unsetting the executability of a file whose executability has not been
 
749
        declared should throw an exception (this may happen when a
 
750
        merge attempts to create a file with a duplicate ID)
 
751
        """
 
752
        transform, root = self.get_transform()
 
753
        wt = transform._tree
 
754
        wt.lock_read()
 
755
        self.addCleanup(wt.unlock)
 
756
        transform.new_file('set_on_creation', root, 'Set on creation', 'soc',
 
757
                           True)
 
758
        sac = transform.new_file('set_after_creation', root,
 
759
                                 'Set after creation', 'sac')
 
760
        transform.set_executability(True, sac)
 
761
        uws = transform.new_file('unset_without_set', root, 'Unset badly',
 
762
                                 'uws')
 
763
        self.assertRaises(KeyError, transform.set_executability, None, uws)
 
764
        transform.apply()
 
765
        self.assertTrue(wt.is_executable('soc'))
 
766
        self.assertTrue(wt.is_executable('sac'))
 
767
 
 
768
    def test_preserve_mode(self):
 
769
        """File mode is preserved when replacing content"""
 
770
        if sys.platform == 'win32':
 
771
            raise TestSkipped('chmod has no effect on win32')
 
772
        transform, root = self.get_transform()
 
773
        transform.new_file('file1', root, 'contents', 'file1-id', True)
 
774
        transform.apply()
 
775
        self.wt.lock_write()
 
776
        self.addCleanup(self.wt.unlock)
 
777
        self.assertTrue(self.wt.is_executable('file1-id'))
 
778
        transform, root = self.get_transform()
 
779
        file1_id = transform.trans_id_tree_file_id('file1-id')
 
780
        transform.delete_contents(file1_id)
 
781
        transform.create_file('contents2', file1_id)
 
782
        transform.apply()
 
783
        self.assertTrue(self.wt.is_executable('file1-id'))
 
784
 
 
785
    def test__set_mode_stats_correctly(self):
 
786
        """_set_mode stats to determine file mode."""
 
787
        if sys.platform == 'win32':
 
788
            raise TestSkipped('chmod has no effect on win32')
 
789
 
 
790
        stat_paths = []
 
791
        real_stat = os.stat
 
792
        def instrumented_stat(path):
 
793
            stat_paths.append(path)
 
794
            return real_stat(path)
 
795
 
 
796
        transform, root = self.get_transform()
 
797
 
 
798
        bar1_id = transform.new_file('bar', root, 'bar contents 1\n',
 
799
                                     file_id='bar-id-1', executable=False)
 
800
        transform.apply()
 
801
 
 
802
        transform, root = self.get_transform()
 
803
        bar1_id = transform.trans_id_tree_path('bar')
 
804
        bar2_id = transform.trans_id_tree_path('bar2')
 
805
        try:
 
806
            os.stat = instrumented_stat
 
807
            transform.create_file('bar2 contents\n', bar2_id, mode_id=bar1_id)
 
808
        finally:
 
809
            os.stat = real_stat
 
810
            transform.finalize()
 
811
 
 
812
        bar1_abspath = self.wt.abspath('bar')
 
813
        self.assertEqual([bar1_abspath], stat_paths)
 
814
 
 
815
    def test_iter_changes(self):
 
816
        self.wt.set_root_id('eert_toor')
 
817
        transform, root = self.get_transform()
 
818
        transform.new_file('old', root, 'blah', 'id-1', True)
 
819
        transform.apply()
 
820
        transform, root = self.get_transform()
 
821
        try:
 
822
            self.assertEqual([], list(transform._iter_changes()))
 
823
            old = transform.trans_id_tree_file_id('id-1')
 
824
            transform.unversion_file(old)
 
825
            self.assertEqual([('id-1', ('old', None), False, (True, False),
 
826
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
827
                (True, True))], list(transform._iter_changes()))
 
828
            transform.new_directory('new', root, 'id-1')
 
829
            self.assertEqual([('id-1', ('old', 'new'), True, (True, True),
 
830
                ('eert_toor', 'eert_toor'), ('old', 'new'),
 
831
                ('file', 'directory'),
 
832
                (True, False))], list(transform._iter_changes()))
 
833
        finally:
 
834
            transform.finalize()
 
835
 
 
836
    def test_iter_changes_new(self):
 
837
        self.wt.set_root_id('eert_toor')
 
838
        transform, root = self.get_transform()
 
839
        transform.new_file('old', root, 'blah')
 
840
        transform.apply()
 
841
        transform, root = self.get_transform()
 
842
        try:
 
843
            old = transform.trans_id_tree_path('old')
 
844
            transform.version_file('id-1', old)
 
845
            self.assertEqual([('id-1', (None, 'old'), False, (False, True),
 
846
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
847
                (False, False))], list(transform._iter_changes()))
 
848
        finally:
 
849
            transform.finalize()
 
850
 
 
851
    def test_iter_changes_modifications(self):
 
852
        self.wt.set_root_id('eert_toor')
 
853
        transform, root = self.get_transform()
 
854
        transform.new_file('old', root, 'blah', 'id-1')
 
855
        transform.new_file('new', root, 'blah')
 
856
        transform.new_directory('subdir', root, 'subdir-id')
 
857
        transform.apply()
 
858
        transform, root = self.get_transform()
 
859
        try:
 
860
            old = transform.trans_id_tree_path('old')
 
861
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
862
            new = transform.trans_id_tree_path('new')
 
863
            self.assertEqual([], list(transform._iter_changes()))
 
864
 
 
865
            #content deletion
 
866
            transform.delete_contents(old)
 
867
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
868
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', None),
 
869
                (False, False))], list(transform._iter_changes()))
 
870
 
 
871
            #content change
 
872
            transform.create_file('blah', old)
 
873
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
874
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
875
                (False, False))], list(transform._iter_changes()))
 
876
            transform.cancel_deletion(old)
 
877
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
878
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
879
                (False, False))], list(transform._iter_changes()))
 
880
            transform.cancel_creation(old)
 
881
 
 
882
            # move file_id to a different file
 
883
            self.assertEqual([], list(transform._iter_changes()))
 
884
            transform.unversion_file(old)
 
885
            transform.version_file('id-1', new)
 
886
            transform.adjust_path('old', root, new)
 
887
            self.assertEqual([('id-1', ('old', 'old'), True, (True, True),
 
888
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
889
                (False, False))], list(transform._iter_changes()))
 
890
            transform.cancel_versioning(new)
 
891
            transform._removed_id = set()
 
892
 
 
893
            #execute bit
 
894
            self.assertEqual([], list(transform._iter_changes()))
 
895
            transform.set_executability(True, old)
 
896
            self.assertEqual([('id-1', ('old', 'old'), False, (True, True),
 
897
                ('eert_toor', 'eert_toor'), ('old', 'old'), ('file', 'file'),
 
898
                (False, True))], list(transform._iter_changes()))
 
899
            transform.set_executability(None, old)
 
900
 
 
901
            # filename
 
902
            self.assertEqual([], list(transform._iter_changes()))
 
903
            transform.adjust_path('new', root, old)
 
904
            transform._new_parent = {}
 
905
            self.assertEqual([('id-1', ('old', 'new'), False, (True, True),
 
906
                ('eert_toor', 'eert_toor'), ('old', 'new'), ('file', 'file'),
 
907
                (False, False))], list(transform._iter_changes()))
 
908
            transform._new_name = {}
 
909
 
 
910
            # parent directory
 
911
            self.assertEqual([], list(transform._iter_changes()))
 
912
            transform.adjust_path('new', subdir, old)
 
913
            transform._new_name = {}
 
914
            self.assertEqual([('id-1', ('old', 'subdir/old'), False,
 
915
                (True, True), ('eert_toor', 'subdir-id'), ('old', 'old'),
 
916
                ('file', 'file'), (False, False))],
 
917
                list(transform._iter_changes()))
 
918
            transform._new_path = {}
 
919
 
 
920
        finally:
 
921
            transform.finalize()
 
922
 
 
923
    def test_iter_changes_modified_bleed(self):
 
924
        self.wt.set_root_id('eert_toor')
 
925
        """Modified flag should not bleed from one change to another"""
 
926
        # unfortunately, we have no guarantee that file1 (which is modified)
 
927
        # will be applied before file2.  And if it's applied after file2, it
 
928
        # obviously can't bleed into file2's change output.  But for now, it
 
929
        # works.
 
930
        transform, root = self.get_transform()
 
931
        transform.new_file('file1', root, 'blah', 'id-1')
 
932
        transform.new_file('file2', root, 'blah', 'id-2')
 
933
        transform.apply()
 
934
        transform, root = self.get_transform()
 
935
        try:
 
936
            transform.delete_contents(transform.trans_id_file_id('id-1'))
 
937
            transform.set_executability(True,
 
938
            transform.trans_id_file_id('id-2'))
 
939
            self.assertEqual([('id-1', (u'file1', u'file1'), True, (True, True),
 
940
                ('eert_toor', 'eert_toor'), ('file1', u'file1'),
 
941
                ('file', None), (False, False)),
 
942
                ('id-2', (u'file2', u'file2'), False, (True, True),
 
943
                ('eert_toor', 'eert_toor'), ('file2', u'file2'),
 
944
                ('file', 'file'), (False, True))],
 
945
                list(transform._iter_changes()))
 
946
        finally:
 
947
            transform.finalize()
 
948
 
 
949
    def test_iter_changes_move_missing(self):
 
950
        """Test moving ids with no files around"""
 
951
        self.wt.set_root_id('toor_eert')
 
952
        # Need two steps because versioning a non-existant file is a conflict.
 
953
        transform, root = self.get_transform()
 
954
        transform.new_directory('floater', root, 'floater-id')
 
955
        transform.apply()
 
956
        transform, root = self.get_transform()
 
957
        transform.delete_contents(transform.trans_id_tree_path('floater'))
 
958
        transform.apply()
 
959
        transform, root = self.get_transform()
 
960
        floater = transform.trans_id_tree_path('floater')
 
961
        try:
 
962
            transform.adjust_path('flitter', root, floater)
 
963
            self.assertEqual([('floater-id', ('floater', 'flitter'), False,
 
964
            (True, True), ('toor_eert', 'toor_eert'), ('floater', 'flitter'),
 
965
            (None, None), (False, False))], list(transform._iter_changes()))
 
966
        finally:
 
967
            transform.finalize()
 
968
 
 
969
    def test_iter_changes_pointless(self):
 
970
        """Ensure that no-ops are not treated as modifications"""
 
971
        self.wt.set_root_id('eert_toor')
 
972
        transform, root = self.get_transform()
 
973
        transform.new_file('old', root, 'blah', 'id-1')
 
974
        transform.new_directory('subdir', root, 'subdir-id')
 
975
        transform.apply()
 
976
        transform, root = self.get_transform()
 
977
        try:
 
978
            old = transform.trans_id_tree_path('old')
 
979
            subdir = transform.trans_id_tree_file_id('subdir-id')
 
980
            self.assertEqual([], list(transform._iter_changes()))
 
981
            transform.delete_contents(subdir)
 
982
            transform.create_directory(subdir)
 
983
            transform.set_executability(False, old)
 
984
            transform.unversion_file(old)
 
985
            transform.version_file('id-1', old)
 
986
            transform.adjust_path('old', root, old)
 
987
            self.assertEqual([], list(transform._iter_changes()))
 
988
        finally:
 
989
            transform.finalize()
 
990
 
 
991
    def test_rename_count(self):
 
992
        transform, root = self.get_transform()
 
993
        transform.new_file('name1', root, 'contents')
 
994
        self.assertEqual(transform.rename_count, 0)
 
995
        transform.apply()
 
996
        self.assertEqual(transform.rename_count, 1)
 
997
        transform2, root = self.get_transform()
 
998
        transform2.adjust_path('name2', root,
 
999
                               transform2.trans_id_tree_path('name1'))
 
1000
        self.assertEqual(transform2.rename_count, 0)
 
1001
        transform2.apply()
 
1002
        self.assertEqual(transform2.rename_count, 2)
 
1003
 
 
1004
    def test_change_parent(self):
 
1005
        """Ensure that after we change a parent, the results are still right.
 
1006
 
 
1007
        Renames and parent changes on pending transforms can happen as part
 
1008
        of conflict resolution, and are explicitly permitted by the
 
1009
        TreeTransform API.
 
1010
 
 
1011
        This test ensures they work correctly with the rename-avoidance
 
1012
        optimization.
 
1013
        """
 
1014
        transform, root = self.get_transform()
 
1015
        parent1 = transform.new_directory('parent1', root)
 
1016
        child1 = transform.new_file('child1', parent1, 'contents')
 
1017
        parent2 = transform.new_directory('parent2', root)
 
1018
        transform.adjust_path('child1', parent2, child1)
 
1019
        transform.apply()
 
1020
        self.failIfExists(self.wt.abspath('parent1/child1'))
 
1021
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1022
        # rename limbo/new-1 => parent1, rename limbo/new-3 => parent2
 
1023
        # no rename for child1 (counting only renames during apply)
 
1024
        self.failUnlessEqual(2, transform.rename_count)
 
1025
 
 
1026
    def test_cancel_parent(self):
 
1027
        """Cancelling a parent doesn't cause deletion of a non-empty directory
 
1028
 
 
1029
        This is like the test_change_parent, except that we cancel the parent
 
1030
        before adjusting the path.  The transform must detect that the
 
1031
        directory is non-empty, and move children to safe locations.
 
1032
        """
 
1033
        transform, root = self.get_transform()
 
1034
        parent1 = transform.new_directory('parent1', root)
 
1035
        child1 = transform.new_file('child1', parent1, 'contents')
 
1036
        child2 = transform.new_file('child2', parent1, 'contents')
 
1037
        try:
 
1038
            transform.cancel_creation(parent1)
 
1039
        except OSError:
 
1040
            self.fail('Failed to move child1 before deleting parent1')
 
1041
        transform.cancel_creation(child2)
 
1042
        transform.create_directory(parent1)
 
1043
        try:
 
1044
            transform.cancel_creation(parent1)
 
1045
        # If the transform incorrectly believes that child2 is still in
 
1046
        # parent1's limbo directory, it will try to rename it and fail
 
1047
        # because was already moved by the first cancel_creation.
 
1048
        except OSError:
 
1049
            self.fail('Transform still thinks child2 is a child of parent1')
 
1050
        parent2 = transform.new_directory('parent2', root)
 
1051
        transform.adjust_path('child1', parent2, child1)
 
1052
        transform.apply()
 
1053
        self.failIfExists(self.wt.abspath('parent1'))
 
1054
        self.failUnlessExists(self.wt.abspath('parent2/child1'))
 
1055
        # rename limbo/new-3 => parent2, rename limbo/new-2 => child1
 
1056
        self.failUnlessEqual(2, transform.rename_count)
 
1057
 
 
1058
    def test_adjust_and_cancel(self):
 
1059
        """Make sure adjust_path keeps track of limbo children properly"""
 
1060
        transform, root = self.get_transform()
 
1061
        parent1 = transform.new_directory('parent1', root)
 
1062
        child1 = transform.new_file('child1', parent1, 'contents')
 
1063
        parent2 = transform.new_directory('parent2', root)
 
1064
        transform.adjust_path('child1', parent2, child1)
 
1065
        transform.cancel_creation(child1)
 
1066
        try:
 
1067
            transform.cancel_creation(parent1)
 
1068
        # if the transform thinks child1 is still in parent1's limbo
 
1069
        # directory, it will attempt to move it and fail.
 
1070
        except OSError:
 
1071
            self.fail('Transform still thinks child1 is a child of parent1')
 
1072
        transform.finalize()
 
1073
 
 
1074
    def test_noname_contents(self):
 
1075
        """TreeTransform should permit deferring naming files."""
 
1076
        transform, root = self.get_transform()
 
1077
        parent = transform.trans_id_file_id('parent-id')
 
1078
        try:
 
1079
            transform.create_directory(parent)
 
1080
        except KeyError:
 
1081
            self.fail("Can't handle contents with no name")
 
1082
        transform.finalize()
 
1083
 
 
1084
    def test_noname_contents_nested(self):
 
1085
        """TreeTransform should permit deferring naming files."""
 
1086
        transform, root = self.get_transform()
 
1087
        parent = transform.trans_id_file_id('parent-id')
 
1088
        try:
 
1089
            transform.create_directory(parent)
 
1090
        except KeyError:
 
1091
            self.fail("Can't handle contents with no name")
 
1092
        child = transform.new_directory('child', parent)
 
1093
        transform.adjust_path('parent', root, parent)
 
1094
        transform.apply()
 
1095
        self.failUnlessExists(self.wt.abspath('parent/child'))
 
1096
        self.assertEqual(1, transform.rename_count)
 
1097
 
 
1098
    def test_reuse_name(self):
 
1099
        """Avoid reusing the same limbo name for different files"""
 
1100
        transform, root = self.get_transform()
 
1101
        parent = transform.new_directory('parent', root)
 
1102
        child1 = transform.new_directory('child', parent)
 
1103
        try:
 
1104
            child2 = transform.new_directory('child', parent)
 
1105
        except OSError:
 
1106
            self.fail('Tranform tried to use the same limbo name twice')
 
1107
        transform.adjust_path('child2', parent, child2)
 
1108
        transform.apply()
 
1109
        # limbo/new-1 => parent, limbo/new-3 => parent/child2
 
1110
        # child2 is put into top-level limbo because child1 has already
 
1111
        # claimed the direct limbo path when child2 is created.  There is no
 
1112
        # advantage in renaming files once they're in top-level limbo, except
 
1113
        # as part of apply.
 
1114
        self.assertEqual(2, transform.rename_count)
 
1115
 
 
1116
    def test_reuse_when_first_moved(self):
 
1117
        """Don't avoid direct paths when it is safe to use them"""
 
1118
        transform, root = self.get_transform()
 
1119
        parent = transform.new_directory('parent', root)
 
1120
        child1 = transform.new_directory('child', parent)
 
1121
        transform.adjust_path('child1', parent, child1)
 
1122
        child2 = transform.new_directory('child', parent)
 
1123
        transform.apply()
 
1124
        # limbo/new-1 => parent
 
1125
        self.assertEqual(1, transform.rename_count)
 
1126
 
 
1127
    def test_reuse_after_cancel(self):
 
1128
        """Don't avoid direct paths when it is safe to use them"""
 
1129
        transform, root = self.get_transform()
 
1130
        parent2 = transform.new_directory('parent2', root)
 
1131
        child1 = transform.new_directory('child1', parent2)
 
1132
        transform.cancel_creation(parent2)
 
1133
        transform.create_directory(parent2)
 
1134
        child2 = transform.new_directory('child1', parent2)
 
1135
        transform.adjust_path('child2', parent2, child1)
 
1136
        transform.apply()
 
1137
        # limbo/new-1 => parent2, limbo/new-2 => parent2/child1
 
1138
        self.assertEqual(2, transform.rename_count)
 
1139
 
 
1140
    def test_finalize_order(self):
 
1141
        """Finalize must be done in child-to-parent order"""
 
1142
        transform, root = self.get_transform()
 
1143
        parent = transform.new_directory('parent', root)
 
1144
        child = transform.new_directory('child', parent)
 
1145
        try:
 
1146
            transform.finalize()
 
1147
        except OSError:
 
1148
            self.fail('Tried to remove parent before child1')
 
1149
 
 
1150
    def test_cancel_with_cancelled_child_should_succeed(self):
 
1151
        transform, root = self.get_transform()
 
1152
        parent = transform.new_directory('parent', root)
 
1153
        child = transform.new_directory('child', parent)
 
1154
        transform.cancel_creation(child)
 
1155
        transform.cancel_creation(parent)
 
1156
        transform.finalize()
 
1157
 
 
1158
    def test_change_entry(self):
 
1159
        txt = 'bzrlib.transform.change_entry was deprecated in version 0.90.'
 
1160
        self.callDeprecated([txt], change_entry, None, None, None, None, None,
 
1161
            None, None, None)
 
1162
 
 
1163
    def test_case_insensitive_clash(self):
 
1164
        self.requireFeature(CaseInsensitiveFilesystemFeature)
 
1165
        def tt_helper():
 
1166
            wt = self.make_branch_and_tree('.')
 
1167
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1168
            try:
 
1169
                tt.new_file('foo', tt.root, 'bar')
 
1170
                tt.new_file('Foo', tt.root, 'spam')
 
1171
                # Lie to tt that we've already resolved all conflicts.
 
1172
                tt.apply(no_conflicts=True)
 
1173
            except:
 
1174
                wt.unlock()
 
1175
                raise
 
1176
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1177
        self.assertContainsRe(str(err),
 
1178
            "^File exists: .+/foo")
 
1179
 
 
1180
    def test_two_directories_clash(self):
 
1181
        def tt_helper():
 
1182
            wt = self.make_branch_and_tree('.')
 
1183
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1184
            try:
 
1185
                foo_1 = tt.new_directory('foo', tt.root)
 
1186
                tt.new_directory('bar', foo_1)
 
1187
                foo_2 = tt.new_directory('foo', tt.root)
 
1188
                tt.new_directory('baz', foo_2)
 
1189
                # Lie to tt that we've already resolved all conflicts.
 
1190
                tt.apply(no_conflicts=True)
 
1191
            except:
 
1192
                wt.unlock()
 
1193
                raise
 
1194
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1195
        self.assertContainsRe(str(err),
 
1196
            "^File exists: .+/foo")
 
1197
 
 
1198
    def test_two_directories_clash_finalize(self):
 
1199
        def tt_helper():
 
1200
            wt = self.make_branch_and_tree('.')
 
1201
            tt = TreeTransform(wt)  # TreeTransform obtains write lock
 
1202
            try:
 
1203
                foo_1 = tt.new_directory('foo', tt.root)
 
1204
                tt.new_directory('bar', foo_1)
 
1205
                foo_2 = tt.new_directory('foo', tt.root)
 
1206
                tt.new_directory('baz', foo_2)
 
1207
                # Lie to tt that we've already resolved all conflicts.
 
1208
                tt.apply(no_conflicts=True)
 
1209
            except:
 
1210
                tt.finalize()
 
1211
                raise
 
1212
        err = self.assertRaises(errors.FileExists, tt_helper)
 
1213
        self.assertContainsRe(str(err),
 
1214
            "^File exists: .+/foo")
492
1215
 
493
1216
 
494
1217
class TransformGroup(object):
495
 
    def __init__(self, dirname):
 
1218
 
 
1219
    def __init__(self, dirname, root_id):
496
1220
        self.name = dirname
497
1221
        os.mkdir(dirname)
498
1222
        self.wt = BzrDir.create_standalone_workingtree(dirname)
 
1223
        self.wt.set_root_id(root_id)
499
1224
        self.b = self.wt.branch
500
1225
        self.tt = TreeTransform(self.wt)
501
1226
        self.root = self.tt.trans_id_tree_file_id(self.wt.get_root_id())
502
1227
 
 
1228
 
503
1229
def conflict_text(tree, merge):
504
1230
    template = '%s TREE\n%s%s\n%s%s MERGE-SOURCE\n'
505
1231
    return template % ('<' * 7, tree, '=' * 7, merge, '>' * 7)
507
1233
 
508
1234
class TestTransformMerge(TestCaseInTempDir):
509
1235
    def test_text_merge(self):
510
 
        base = TransformGroup("base")
 
1236
        root_id = generate_ids.gen_root_id()
 
1237
        base = TransformGroup("base", root_id)
511
1238
        base.tt.new_file('a', base.root, 'a\nb\nc\nd\be\n', 'a')
512
1239
        base.tt.new_file('b', base.root, 'b1', 'b')
513
1240
        base.tt.new_file('c', base.root, 'c', 'c')
517
1244
        base.tt.new_directory('g', base.root, 'g')
518
1245
        base.tt.new_directory('h', base.root, 'h')
519
1246
        base.tt.apply()
520
 
        other = TransformGroup("other")
 
1247
        other = TransformGroup("other", root_id)
521
1248
        other.tt.new_file('a', other.root, 'y\nb\nc\nd\be\n', 'a')
522
1249
        other.tt.new_file('b', other.root, 'b2', 'b')
523
1250
        other.tt.new_file('c', other.root, 'c2', 'c')
528
1255
        other.tt.new_file('h', other.root, 'h\ni\nj\nk\n', 'h')
529
1256
        other.tt.new_file('i', other.root, 'h\ni\nj\nk\n', 'i')
530
1257
        other.tt.apply()
531
 
        this = TransformGroup("this")
 
1258
        this = TransformGroup("this", root_id)
532
1259
        this.tt.new_file('a', this.root, 'a\nb\nc\nd\bz\n', 'a')
533
1260
        this.tt.new_file('b', this.root, 'b', 'b')
534
1261
        this.tt.new_file('c', this.root, 'c', 'c')
540
1267
        this.tt.new_file('i', this.root, '1\n2\n3\n4\n', 'i')
541
1268
        this.tt.apply()
542
1269
        Merge3Merger(this.wt, this.wt, base.wt, other.wt)
 
1270
 
543
1271
        # textual merge
544
1272
        self.assertEqual(this.wt.get_file('a').read(), 'y\nb\nc\nd\bz\n')
545
1273
        # three-way text conflict
580
1308
        self.assertSubset(merge_modified, modified)
581
1309
        self.assertEqual(len(merge_modified), len(modified))
582
1310
        this.wt.remove('b')
583
 
        this.wt.revert([])
 
1311
        this.wt.revert()
584
1312
 
585
1313
    def test_file_merge(self):
586
 
        if not has_symlinks():
587
 
            raise TestSkipped('Symlinks are not supported on this platform')
588
 
        base = TransformGroup("BASE")
589
 
        this = TransformGroup("THIS")
590
 
        other = TransformGroup("OTHER")
 
1314
        self.requireFeature(SymlinkFeature)
 
1315
        root_id = generate_ids.gen_root_id()
 
1316
        base = TransformGroup("BASE", root_id)
 
1317
        this = TransformGroup("THIS", root_id)
 
1318
        other = TransformGroup("OTHER", root_id)
591
1319
        for tg in this, base, other:
592
1320
            tg.tt.new_directory('a', tg.root, 'a')
593
1321
            tg.tt.new_symlink('b', tg.root, 'b', 'b')
625
1353
        self.assertIs(os.path.lexists(this.wt.abspath('h.OTHER')), True)
626
1354
 
627
1355
    def test_filename_merge(self):
628
 
        base = TransformGroup("BASE")
629
 
        this = TransformGroup("THIS")
630
 
        other = TransformGroup("OTHER")
 
1356
        root_id = generate_ids.gen_root_id()
 
1357
        base = TransformGroup("BASE", root_id)
 
1358
        this = TransformGroup("THIS", root_id)
 
1359
        other = TransformGroup("OTHER", root_id)
631
1360
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
632
1361
                                   for t in [base, this, other]]
633
1362
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
657
1386
        self.assertEqual(this.wt.id2path('f'), pathjoin('b/f1'))
658
1387
 
659
1388
    def test_filename_merge_conflicts(self):
660
 
        base = TransformGroup("BASE")
661
 
        this = TransformGroup("THIS")
662
 
        other = TransformGroup("OTHER")
 
1389
        root_id = generate_ids.gen_root_id()
 
1390
        base = TransformGroup("BASE", root_id)
 
1391
        this = TransformGroup("THIS", root_id)
 
1392
        other = TransformGroup("OTHER", root_id)
663
1393
        base_a, this_a, other_a = [t.tt.new_directory('a', t.root, 'a') 
664
1394
                                   for t in [base, this, other]]
665
1395
        base_b, this_b, other_b = [t.tt.new_directory('b', t.root, 'b') 
686
1416
        self.assertIs(os.path.lexists(this.wt.abspath('b/h1.OTHER')), False)
687
1417
        self.assertEqual(this.wt.id2path('i'), pathjoin('b/i1.OTHER'))
688
1418
 
689
 
class TestBuildTree(TestCaseInTempDir):
690
 
    def test_build_tree(self):
691
 
        if not has_symlinks():
692
 
            raise TestSkipped('Test requires symlink support')
 
1419
 
 
1420
class TestBuildTree(tests.TestCaseWithTransport):
 
1421
 
 
1422
    def test_build_tree_with_symlinks(self):
 
1423
        self.requireFeature(SymlinkFeature)
693
1424
        os.mkdir('a')
694
1425
        a = BzrDir.create_standalone_workingtree('a')
695
1426
        os.mkdir('a/foo')
698
1429
        a.add(['foo', 'foo/bar', 'foo/baz'])
699
1430
        a.commit('initial commit')
700
1431
        b = BzrDir.create_standalone_workingtree('b')
701
 
        build_tree(a.basis_tree(), b)
 
1432
        basis = a.basis_tree()
 
1433
        basis.lock_read()
 
1434
        self.addCleanup(basis.unlock)
 
1435
        build_tree(basis, b)
702
1436
        self.assertIs(os.path.isdir('b/foo'), True)
703
1437
        self.assertEqual(file('b/foo/bar', 'rb').read(), "contents")
704
1438
        self.assertEqual(os.readlink('b/foo/baz'), 'a/foo/bar')
 
1439
 
 
1440
    def test_build_with_references(self):
 
1441
        tree = self.make_branch_and_tree('source',
 
1442
            format='dirstate-with-subtree')
 
1443
        subtree = self.make_branch_and_tree('source/subtree',
 
1444
            format='dirstate-with-subtree')
 
1445
        tree.add_reference(subtree)
 
1446
        tree.commit('a revision')
 
1447
        tree.branch.create_checkout('target')
 
1448
        self.failUnlessExists('target')
 
1449
        self.failUnlessExists('target/subtree')
 
1450
 
 
1451
    def test_file_conflict_handling(self):
 
1452
        """Ensure that when building trees, conflict handling is done"""
 
1453
        source = self.make_branch_and_tree('source')
 
1454
        target = self.make_branch_and_tree('target')
 
1455
        self.build_tree(['source/file', 'target/file'])
 
1456
        source.add('file', 'new-file')
 
1457
        source.commit('added file')
 
1458
        build_tree(source.basis_tree(), target)
 
1459
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1460
                          'file.moved', 'file', None, 'new-file')],
 
1461
                         target.conflicts())
 
1462
        target2 = self.make_branch_and_tree('target2')
 
1463
        target_file = file('target2/file', 'wb')
 
1464
        try:
 
1465
            source_file = file('source/file', 'rb')
 
1466
            try:
 
1467
                target_file.write(source_file.read())
 
1468
            finally:
 
1469
                source_file.close()
 
1470
        finally:
 
1471
            target_file.close()
 
1472
        build_tree(source.basis_tree(), target2)
 
1473
        self.assertEqual([], target2.conflicts())
 
1474
 
 
1475
    def test_symlink_conflict_handling(self):
 
1476
        """Ensure that when building trees, conflict handling is done"""
 
1477
        self.requireFeature(SymlinkFeature)
 
1478
        source = self.make_branch_and_tree('source')
 
1479
        os.symlink('foo', 'source/symlink')
 
1480
        source.add('symlink', 'new-symlink')
 
1481
        source.commit('added file')
 
1482
        target = self.make_branch_and_tree('target')
 
1483
        os.symlink('bar', 'target/symlink')
 
1484
        build_tree(source.basis_tree(), target)
 
1485
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1486
            'symlink.moved', 'symlink', None, 'new-symlink')],
 
1487
            target.conflicts())
 
1488
        target = self.make_branch_and_tree('target2')
 
1489
        os.symlink('foo', 'target2/symlink')
 
1490
        build_tree(source.basis_tree(), target)
 
1491
        self.assertEqual([], target.conflicts())
705
1492
        
 
1493
    def test_directory_conflict_handling(self):
 
1494
        """Ensure that when building trees, conflict handling is done"""
 
1495
        source = self.make_branch_and_tree('source')
 
1496
        target = self.make_branch_and_tree('target')
 
1497
        self.build_tree(['source/dir1/', 'source/dir1/file', 'target/dir1/'])
 
1498
        source.add(['dir1', 'dir1/file'], ['new-dir1', 'new-file'])
 
1499
        source.commit('added file')
 
1500
        build_tree(source.basis_tree(), target)
 
1501
        self.assertEqual([], target.conflicts())
 
1502
        self.failUnlessExists('target/dir1/file')
 
1503
 
 
1504
        # Ensure contents are merged
 
1505
        target = self.make_branch_and_tree('target2')
 
1506
        self.build_tree(['target2/dir1/', 'target2/dir1/file2'])
 
1507
        build_tree(source.basis_tree(), target)
 
1508
        self.assertEqual([], target.conflicts())
 
1509
        self.failUnlessExists('target2/dir1/file2')
 
1510
        self.failUnlessExists('target2/dir1/file')
 
1511
 
 
1512
        # Ensure new contents are suppressed for existing branches
 
1513
        target = self.make_branch_and_tree('target3')
 
1514
        self.make_branch('target3/dir1')
 
1515
        self.build_tree(['target3/dir1/file2'])
 
1516
        build_tree(source.basis_tree(), target)
 
1517
        self.failIfExists('target3/dir1/file')
 
1518
        self.failUnlessExists('target3/dir1/file2')
 
1519
        self.failUnlessExists('target3/dir1.diverted/file')
 
1520
        self.assertEqual([DuplicateEntry('Diverted to',
 
1521
            'dir1.diverted', 'dir1', 'new-dir1', None)],
 
1522
            target.conflicts())
 
1523
 
 
1524
        target = self.make_branch_and_tree('target4')
 
1525
        self.build_tree(['target4/dir1/'])
 
1526
        self.make_branch('target4/dir1/file')
 
1527
        build_tree(source.basis_tree(), target)
 
1528
        self.failUnlessExists('target4/dir1/file')
 
1529
        self.assertEqual('directory', file_kind('target4/dir1/file'))
 
1530
        self.failUnlessExists('target4/dir1/file.diverted')
 
1531
        self.assertEqual([DuplicateEntry('Diverted to',
 
1532
            'dir1/file.diverted', 'dir1/file', 'new-file', None)],
 
1533
            target.conflicts())
 
1534
 
 
1535
    def test_mixed_conflict_handling(self):
 
1536
        """Ensure that when building trees, conflict handling is done"""
 
1537
        source = self.make_branch_and_tree('source')
 
1538
        target = self.make_branch_and_tree('target')
 
1539
        self.build_tree(['source/name', 'target/name/'])
 
1540
        source.add('name', 'new-name')
 
1541
        source.commit('added file')
 
1542
        build_tree(source.basis_tree(), target)
 
1543
        self.assertEqual([DuplicateEntry('Moved existing file to',
 
1544
            'name.moved', 'name', None, 'new-name')], target.conflicts())
 
1545
 
 
1546
    def test_raises_in_populated(self):
 
1547
        source = self.make_branch_and_tree('source')
 
1548
        self.build_tree(['source/name'])
 
1549
        source.add('name')
 
1550
        source.commit('added name')
 
1551
        target = self.make_branch_and_tree('target')
 
1552
        self.build_tree(['target/name'])
 
1553
        target.add('name')
 
1554
        self.assertRaises(errors.WorkingTreeAlreadyPopulated, 
 
1555
            build_tree, source.basis_tree(), target)
 
1556
 
 
1557
    def test_build_tree_rename_count(self):
 
1558
        source = self.make_branch_and_tree('source')
 
1559
        self.build_tree(['source/file1', 'source/dir1/'])
 
1560
        source.add(['file1', 'dir1'])
 
1561
        source.commit('add1')
 
1562
        target1 = self.make_branch_and_tree('target1')
 
1563
        transform_result = build_tree(source.basis_tree(), target1)
 
1564
        self.assertEqual(2, transform_result.rename_count)
 
1565
 
 
1566
        self.build_tree(['source/dir1/file2'])
 
1567
        source.add(['dir1/file2'])
 
1568
        source.commit('add3')
 
1569
        target2 = self.make_branch_and_tree('target2')
 
1570
        transform_result = build_tree(source.basis_tree(), target2)
 
1571
        # children of non-root directories should not be renamed
 
1572
        self.assertEqual(2, transform_result.rename_count)
 
1573
 
 
1574
    def test_build_tree_accelerator_tree(self):
 
1575
        source = self.make_branch_and_tree('source')
 
1576
        self.build_tree_contents([('source/file1', 'A')])
 
1577
        self.build_tree_contents([('source/file2', 'B')])
 
1578
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
 
1579
        source.commit('commit files')
 
1580
        self.build_tree_contents([('source/file2', 'C')])
 
1581
        calls = []
 
1582
        real_source_get_file = source.get_file
 
1583
        def get_file(file_id, path=None):
 
1584
            calls.append(file_id)
 
1585
            return real_source_get_file(file_id, path)
 
1586
        source.get_file = get_file
 
1587
        source.lock_read()
 
1588
        self.addCleanup(source.unlock)
 
1589
        target = self.make_branch_and_tree('target')
 
1590
        revision_tree = source.basis_tree()
 
1591
        revision_tree.lock_read()
 
1592
        self.addCleanup(revision_tree.unlock)
 
1593
        build_tree(revision_tree, target, source)
 
1594
        self.assertEqual(['file1-id'], calls)
 
1595
        target.lock_read()
 
1596
        self.addCleanup(target.unlock)
 
1597
        self.assertEqual([], list(target._iter_changes(revision_tree)))
 
1598
 
 
1599
    def test_build_tree_accelerator_tree_missing_file(self):
 
1600
        source = self.make_branch_and_tree('source')
 
1601
        self.build_tree_contents([('source/file1', 'A')])
 
1602
        self.build_tree_contents([('source/file2', 'B')])
 
1603
        source.add(['file1', 'file2'])
 
1604
        source.commit('commit files')
 
1605
        os.unlink('source/file1')
 
1606
        source.remove(['file2'])
 
1607
        target = self.make_branch_and_tree('target')
 
1608
        revision_tree = source.basis_tree()
 
1609
        revision_tree.lock_read()
 
1610
        self.addCleanup(revision_tree.unlock)
 
1611
        build_tree(revision_tree, target, source)
 
1612
        target.lock_read()
 
1613
        self.addCleanup(target.unlock)
 
1614
        self.assertEqual([], list(target._iter_changes(revision_tree)))
 
1615
 
 
1616
    def test_build_tree_accelerator_wrong_kind(self):
 
1617
        self.requireFeature(SymlinkFeature)
 
1618
        source = self.make_branch_and_tree('source')
 
1619
        self.build_tree_contents([('source/file1', '')])
 
1620
        self.build_tree_contents([('source/file2', '')])
 
1621
        source.add(['file1', 'file2'], ['file1-id', 'file2-id'])
 
1622
        source.commit('commit files')
 
1623
        os.unlink('source/file2')
 
1624
        self.build_tree_contents([('source/file2/', 'C')])
 
1625
        os.unlink('source/file1')
 
1626
        os.symlink('file2', 'source/file1')
 
1627
        calls = []
 
1628
        real_source_get_file = source.get_file
 
1629
        def get_file(file_id, path=None):
 
1630
            calls.append(file_id)
 
1631
            return real_source_get_file(file_id, path)
 
1632
        source.get_file = get_file
 
1633
        source.lock_read()
 
1634
        self.addCleanup(source.unlock)
 
1635
        target = self.make_branch_and_tree('target')
 
1636
        revision_tree = source.basis_tree()
 
1637
        revision_tree.lock_read()
 
1638
        self.addCleanup(revision_tree.unlock)
 
1639
        build_tree(revision_tree, target, source)
 
1640
        self.assertEqual([], calls)
 
1641
        target.lock_read()
 
1642
        self.addCleanup(target.unlock)
 
1643
        self.assertEqual([], list(target._iter_changes(revision_tree)))
 
1644
 
 
1645
    def test_build_tree_accelerator_tree_moved(self):
 
1646
        source = self.make_branch_and_tree('source')
 
1647
        self.build_tree_contents([('source/file1', 'A')])
 
1648
        source.add(['file1'], ['file1-id'])
 
1649
        source.commit('commit files')
 
1650
        source.rename_one('file1', 'file2')
 
1651
        source.lock_read()
 
1652
        self.addCleanup(source.unlock)
 
1653
        target = self.make_branch_and_tree('target')
 
1654
        revision_tree = source.basis_tree()
 
1655
        revision_tree.lock_read()
 
1656
        self.addCleanup(revision_tree.unlock)
 
1657
        build_tree(revision_tree, target, source)
 
1658
        target.lock_read()
 
1659
        self.addCleanup(target.unlock)
 
1660
        self.assertEqual([], list(target._iter_changes(revision_tree)))
 
1661
 
 
1662
 
706
1663
class MockTransform(object):
707
1664
 
708
1665
    def has_named_child(self, by_parent, parent_id, name):
714
1671
                return True
715
1672
        return False
716
1673
 
 
1674
 
717
1675
class MockEntry(object):
718
1676
    def __init__(self):
719
1677
        object.__init__(self)
720
1678
        self.name = "name"
721
1679
 
 
1680
 
722
1681
class TestGetBackupName(TestCase):
723
1682
    def test_get_backup_name(self):
724
1683
        tt = MockTransform()
732
1691
        self.assertEqual(name, 'name.~1~')
733
1692
        name = get_backup_name(MockEntry(), {'a':['1', '2', '3']}, 'a', tt)
734
1693
        self.assertEqual(name, 'name.~4~')
 
1694
 
 
1695
 
 
1696
class TestFileMover(tests.TestCaseWithTransport):
 
1697
 
 
1698
    def test_file_mover(self):
 
1699
        self.build_tree(['a/', 'a/b', 'c/', 'c/d'])
 
1700
        mover = _FileMover()
 
1701
        mover.rename('a', 'q')
 
1702
        self.failUnlessExists('q')
 
1703
        self.failIfExists('a')
 
1704
        self.failUnlessExists('q/b')
 
1705
        self.failUnlessExists('c')
 
1706
        self.failUnlessExists('c/d')
 
1707
 
 
1708
    def test_pre_delete_rollback(self):
 
1709
        self.build_tree(['a/'])
 
1710
        mover = _FileMover()
 
1711
        mover.pre_delete('a', 'q')
 
1712
        self.failUnlessExists('q')
 
1713
        self.failIfExists('a')
 
1714
        mover.rollback()
 
1715
        self.failIfExists('q')
 
1716
        self.failUnlessExists('a')
 
1717
 
 
1718
    def test_apply_deletions(self):
 
1719
        self.build_tree(['a/', 'b/'])
 
1720
        mover = _FileMover()
 
1721
        mover.pre_delete('a', 'q')
 
1722
        mover.pre_delete('b', 'r')
 
1723
        self.failUnlessExists('q')
 
1724
        self.failUnlessExists('r')
 
1725
        self.failIfExists('a')
 
1726
        self.failIfExists('b')
 
1727
        mover.apply_deletions()
 
1728
        self.failIfExists('q')
 
1729
        self.failIfExists('r')
 
1730
        self.failIfExists('a')
 
1731
        self.failIfExists('b')
 
1732
 
 
1733
    def test_file_mover_rollback(self):
 
1734
        self.build_tree(['a/', 'a/b', 'c/', 'c/d/', 'c/e/'])
 
1735
        mover = _FileMover()
 
1736
        mover.rename('c/d', 'c/f')
 
1737
        mover.rename('c/e', 'c/d')
 
1738
        try:
 
1739
            mover.rename('a', 'c')
 
1740
        except errors.FileExists, e:
 
1741
            mover.rollback()
 
1742
        self.failUnlessExists('a')
 
1743
        self.failUnlessExists('c/d')
 
1744
 
 
1745
 
 
1746
class Bogus(Exception):
 
1747
    pass
 
1748
 
 
1749
 
 
1750
class TestTransformRollback(tests.TestCaseWithTransport):
 
1751
 
 
1752
    class ExceptionFileMover(_FileMover):
 
1753
 
 
1754
        def __init__(self, bad_source=None, bad_target=None):
 
1755
            _FileMover.__init__(self)
 
1756
            self.bad_source = bad_source
 
1757
            self.bad_target = bad_target
 
1758
 
 
1759
        def rename(self, source, target):
 
1760
            if (self.bad_source is not None and
 
1761
                source.endswith(self.bad_source)):
 
1762
                raise Bogus
 
1763
            elif (self.bad_target is not None and
 
1764
                target.endswith(self.bad_target)):
 
1765
                raise Bogus
 
1766
            else:
 
1767
                _FileMover.rename(self, source, target)
 
1768
 
 
1769
    def test_rollback_rename(self):
 
1770
        tree = self.make_branch_and_tree('.')
 
1771
        self.build_tree(['a/', 'a/b'])
 
1772
        tt = TreeTransform(tree)
 
1773
        self.addCleanup(tt.finalize)
 
1774
        a_id = tt.trans_id_tree_path('a')
 
1775
        tt.adjust_path('c', tt.root, a_id)
 
1776
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1777
        self.assertRaises(Bogus, tt.apply,
 
1778
                          _mover=self.ExceptionFileMover(bad_source='a'))
 
1779
        self.failUnlessExists('a')
 
1780
        self.failUnlessExists('a/b')
 
1781
        tt.apply()
 
1782
        self.failUnlessExists('c')
 
1783
        self.failUnlessExists('c/d')
 
1784
 
 
1785
    def test_rollback_rename_into_place(self):
 
1786
        tree = self.make_branch_and_tree('.')
 
1787
        self.build_tree(['a/', 'a/b'])
 
1788
        tt = TreeTransform(tree)
 
1789
        self.addCleanup(tt.finalize)
 
1790
        a_id = tt.trans_id_tree_path('a')
 
1791
        tt.adjust_path('c', tt.root, a_id)
 
1792
        tt.adjust_path('d', a_id, tt.trans_id_tree_path('a/b'))
 
1793
        self.assertRaises(Bogus, tt.apply,
 
1794
                          _mover=self.ExceptionFileMover(bad_target='c/d'))
 
1795
        self.failUnlessExists('a')
 
1796
        self.failUnlessExists('a/b')
 
1797
        tt.apply()
 
1798
        self.failUnlessExists('c')
 
1799
        self.failUnlessExists('c/d')
 
1800
 
 
1801
    def test_rollback_deletion(self):
 
1802
        tree = self.make_branch_and_tree('.')
 
1803
        self.build_tree(['a/', 'a/b'])
 
1804
        tt = TreeTransform(tree)
 
1805
        self.addCleanup(tt.finalize)
 
1806
        a_id = tt.trans_id_tree_path('a')
 
1807
        tt.delete_contents(a_id)
 
1808
        tt.adjust_path('d', tt.root, tt.trans_id_tree_path('a/b'))
 
1809
        self.assertRaises(Bogus, tt.apply,
 
1810
                          _mover=self.ExceptionFileMover(bad_target='d'))
 
1811
        self.failUnlessExists('a')
 
1812
        self.failUnlessExists('a/b')
 
1813
 
 
1814
    def test_resolve_no_parent(self):
 
1815
        wt = self.make_branch_and_tree('.')
 
1816
        tt = TreeTransform(wt)
 
1817
        self.addCleanup(tt.finalize)
 
1818
        parent = tt.trans_id_file_id('parent-id')
 
1819
        tt.new_file('file', parent, 'Contents')
 
1820
        resolve_conflicts(tt)
 
1821
 
 
1822
 
 
1823
class TestTransformPreview(tests.TestCaseWithTransport):
 
1824
 
 
1825
    def create_tree(self):
 
1826
        tree = self.make_branch_and_tree('.')
 
1827
        self.build_tree_contents([('a', 'content 1')])
 
1828
        tree.add('a', 'a-id')
 
1829
        tree.commit('rev1', rev_id='rev1')
 
1830
        return tree.branch.repository.revision_tree('rev1')
 
1831
 
 
1832
    def get_empty_preview(self):
 
1833
        repository = self.make_repository('repo')
 
1834
        tree = repository.revision_tree(_mod_revision.NULL_REVISION)
 
1835
        return TransformPreview(tree)
 
1836
 
 
1837
    def test_transform_preview(self):
 
1838
        revision_tree = self.create_tree()
 
1839
        preview = TransformPreview(revision_tree)
 
1840
 
 
1841
    def test_transform_preview_tree(self):
 
1842
        revision_tree = self.create_tree()
 
1843
        preview = TransformPreview(revision_tree)
 
1844
        preview.get_preview_tree()
 
1845
 
 
1846
    def test_transform_new_file(self):
 
1847
        revision_tree = self.create_tree()
 
1848
        preview = TransformPreview(revision_tree)
 
1849
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
1850
        preview_tree = preview.get_preview_tree()
 
1851
        self.assertEqual(preview_tree.kind('file2-id'), 'file')
 
1852
        self.assertEqual(
 
1853
            preview_tree.get_file('file2-id').read(), 'content B\n')
 
1854
 
 
1855
    def test_diff_preview_tree(self):
 
1856
        revision_tree = self.create_tree()
 
1857
        preview = TransformPreview(revision_tree)
 
1858
        preview.new_file('file2', preview.root, 'content B\n', 'file2-id')
 
1859
        preview_tree = preview.get_preview_tree()
 
1860
        out = StringIO()
 
1861
        show_diff_trees(revision_tree, preview_tree, out)
 
1862
        lines = out.getvalue().splitlines()
 
1863
        self.assertEqual(lines[0], "=== added file 'file2'")
 
1864
        # 3 lines of diff administrivia
 
1865
        self.assertEqual(lines[4], "+content B")
 
1866
 
 
1867
    def test_transform_conflicts(self):
 
1868
        revision_tree = self.create_tree()
 
1869
        preview = TransformPreview(revision_tree)
 
1870
        preview.new_file('a', preview.root, 'content 2')
 
1871
        resolve_conflicts(preview)
 
1872
        trans_id = preview.trans_id_file_id('a-id')
 
1873
        self.assertEqual('a.moved', preview.final_name(trans_id))
 
1874
 
 
1875
    def get_tree_and_preview_tree(self):
 
1876
        revision_tree = self.create_tree()
 
1877
        preview = TransformPreview(revision_tree)
 
1878
        a_trans_id = preview.trans_id_file_id('a-id')
 
1879
        preview.delete_contents(a_trans_id)
 
1880
        preview.create_file('b content', a_trans_id)
 
1881
        preview_tree = preview.get_preview_tree()
 
1882
        return revision_tree, preview_tree
 
1883
 
 
1884
    def test_iter_changes(self):
 
1885
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1886
        root = revision_tree.inventory.root.file_id
 
1887
        self.assertEqual([('a-id', ('a', 'a'), True, (True, True),
 
1888
                          (root, root), ('a', 'a'), ('file', 'file'),
 
1889
                          (False, False))],
 
1890
                          list(preview_tree._iter_changes(revision_tree)))
 
1891
 
 
1892
    def test_wrong_tree_value_error(self):
 
1893
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1894
        e = self.assertRaises(ValueError, preview_tree._iter_changes,
 
1895
                              preview_tree)
 
1896
        self.assertEqual('from_tree must be transform source tree.', str(e))
 
1897
 
 
1898
    def test_include_unchanged_value_error(self):
 
1899
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1900
        e = self.assertRaises(ValueError, preview_tree._iter_changes,
 
1901
                              revision_tree, include_unchanged=True)
 
1902
        self.assertEqual('include_unchanged is not supported', str(e))
 
1903
 
 
1904
    def test_specific_files(self):
 
1905
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1906
        e = self.assertRaises(ValueError, preview_tree._iter_changes,
 
1907
                              revision_tree, specific_files=['pete'])
 
1908
        self.assertEqual('specific_files is not supported', str(e))
 
1909
 
 
1910
    def test_want_unversioned_value_error(self):
 
1911
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1912
        e = self.assertRaises(ValueError, preview_tree._iter_changes,
 
1913
                              revision_tree, want_unversioned=True)
 
1914
        self.assertEqual('want_unversioned is not supported', str(e))
 
1915
 
 
1916
    def test_ignore_extra_trees_no_specific_files(self):
 
1917
        # extra_trees is harmless without specific_files, so we'll silently
 
1918
        # accept it, even though we won't use it.
 
1919
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1920
        preview_tree._iter_changes(revision_tree, extra_trees=[preview_tree])
 
1921
 
 
1922
    def test_ignore_require_versioned_no_specific_files(self):
 
1923
        # require_versioned is meaningless without specific_files.
 
1924
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1925
        preview_tree._iter_changes(revision_tree, require_versioned=False)
 
1926
 
 
1927
    def test_ignore_pb(self):
 
1928
        # pb could be supported, but TT.iter_changes doesn't support it.
 
1929
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
 
1930
        preview_tree._iter_changes(revision_tree, pb=progress.DummyProgress())
 
1931
 
 
1932
    def test_kind(self):
 
1933
        revision_tree = self.create_tree()
 
1934
        preview = TransformPreview(revision_tree)
 
1935
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
1936
        preview.new_directory('directory', preview.root, 'dir-id')
 
1937
        preview_tree = preview.get_preview_tree()
 
1938
        self.assertEqual('file', preview_tree.kind('file-id'))
 
1939
        self.assertEqual('directory', preview_tree.kind('dir-id'))
 
1940
 
 
1941
    def test_get_file_mtime(self):
 
1942
        preview = self.get_empty_preview()
 
1943
        file_trans_id = preview.new_file('file', preview.root, 'contents',
 
1944
                                         'file-id')
 
1945
        limbo_path = preview._limbo_name(file_trans_id)
 
1946
        preview_tree = preview.get_preview_tree()
 
1947
        self.assertEqual(os.stat(limbo_path).st_mtime,
 
1948
                         preview_tree.get_file_mtime('file-id'))
 
1949
 
 
1950
    def test_get_file(self):
 
1951
        preview = self.get_empty_preview()
 
1952
        preview.new_file('file', preview.root, 'contents', 'file-id')
 
1953
        preview_tree = preview.get_preview_tree()
 
1954
        tree_file = preview_tree.get_file('file-id')
 
1955
        try:
 
1956
            self.assertEqual('contents', tree_file.read())
 
1957
        finally:
 
1958
            tree_file.close()