/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: Canonical.com Patch Queue Manager
  • Date: 2008-02-23 16:08:08 UTC
  • mfrom: (3220.1.5 bug.107593)
  • Revision ID: pqm@pqm.ubuntu.com-20080223160808-81yb25z7tmhlss8q
(Dmitry Vasiliev) Restore auto-detection of plink.exe on Windows
 (bug #107593)

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