~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/tests/test_transform.py

  • Committer: Andrew Bennetts
  • Date: 2010-08-31 07:12:18 UTC
  • mto: This revision was merged to the branch mainline in revision 5401.
  • Revision ID: andrew.bennetts@canonical.com-20100831071218-4kjieu3ejqcdmdom
Use has_id rather than __contains__; expand NEWS entry; add What's New entry.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright (C) 2006, 2007, 2008 Canonical Ltd
 
1
# Copyright (C) 2006-2010 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
15
15
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16
16
 
17
17
import os
18
 
import stat
19
18
from StringIO import StringIO
20
19
import sys
 
20
import time
21
21
 
22
22
from bzrlib import (
 
23
    bencode,
23
24
    errors,
 
25
    filters,
24
26
    generate_ids,
25
27
    osutils,
26
 
    progress,
27
28
    revision as _mod_revision,
28
 
    symbol_versioning,
 
29
    rules,
29
30
    tests,
30
31
    urlutils,
31
32
    )
32
33
from bzrlib.bzrdir import BzrDir
33
 
from bzrlib.conflicts import (DuplicateEntry, DuplicateID, MissingParent,
34
 
                              UnversionedParent, ParentLoop, DeletingParent,
35
 
                              NonDirectoryParent)
 
34
from bzrlib.conflicts import (
 
35
    DeletingParent,
 
36
    DuplicateEntry,
 
37
    DuplicateID,
 
38
    MissingParent,
 
39
    NonDirectoryParent,
 
40
    ParentLoop,
 
41
    UnversionedParent,
 
42
)
36
43
from bzrlib.diff import show_diff_trees
37
 
from bzrlib.errors import (DuplicateKey, MalformedTransform, NoSuchFile,
38
 
                           ReusingTransform, CantMoveRoot,
39
 
                           PathsNotVersionedError, ExistingLimbo,
40
 
                           ExistingPendingDeletion, ImmortalLimbo,
41
 
                           ImmortalPendingDeletion, LockError)
42
 
from bzrlib.osutils import file_kind, pathjoin
 
44
from bzrlib.errors import (
 
45
    DuplicateKey,
 
46
    ExistingLimbo,
 
47
    ExistingPendingDeletion,
 
48
    ImmortalLimbo,
 
49
    ImmortalPendingDeletion,
 
50
    LockError,
 
51
    MalformedTransform,
 
52
    NoSuchFile,
 
53
    ReusingTransform,
 
54
)
 
55
from bzrlib.osutils import (
 
56
    file_kind,
 
57
    pathjoin,
 
58
)
43
59
from bzrlib.merge import Merge3Merger, Merger
44
60
from bzrlib.tests import (
45
61
    HardlinkFeature,
47
63
    TestCase,
48
64
    TestCaseInTempDir,
49
65
    TestSkipped,
50
 
    )
51
 
from bzrlib.transform import (TreeTransform, ROOT_PARENT, FinalPaths,
52
 
                              resolve_conflicts, cook_conflicts,
53
 
                              build_tree, get_backup_name,
54
 
                              _FileMover, resolve_checkout,
55
 
                              TransformPreview, create_from_tree)
56
 
from bzrlib.util import bencode
 
66
)
 
67
from bzrlib.transform import (
 
68
    build_tree,
 
69
    create_from_tree,
 
70
    cook_conflicts,
 
71
    _FileMover,
 
72
    FinalPaths,
 
73
    get_backup_name,
 
74
    resolve_conflicts,
 
75
    resolve_checkout,
 
76
    ROOT_PARENT,
 
77
    TransformPreview,
 
78
    TreeTransform,
 
79
)
57
80
 
58
81
 
59
82
class TestTreeTransform(tests.TestCaseWithTransport):
136
159
        transform.finalize()
137
160
        transform.finalize()
138
161
 
 
162
    def test_create_files_same_timestamp(self):
 
163
        transform, root = self.get_transform()
 
164
        self.wt.lock_tree_write()
 
165
        self.addCleanup(self.wt.unlock)
 
166
        # Roll back the clock, so that we know everything is being set to the
 
167
        # exact time
 
168
        transform._creation_mtime = creation_mtime = time.time() - 20.0
 
169
        transform.create_file('content-one',
 
170
                              transform.create_path('one', root))
 
171
        time.sleep(1) # *ugly*
 
172
        transform.create_file('content-two',
 
173
                              transform.create_path('two', root))
 
174
        transform.apply()
 
175
        fo, st1 = self.wt.get_file_with_stat(None, path='one', filtered=False)
 
176
        fo.close()
 
177
        fo, st2 = self.wt.get_file_with_stat(None, path='two', filtered=False)
 
178
        fo.close()
 
179
        # We only guarantee 2s resolution
 
180
        self.assertTrue(abs(creation_mtime - st1.st_mtime) < 2.0,
 
181
            "%s != %s within 2 seconds" % (creation_mtime, st1.st_mtime))
 
182
        # But if we have more than that, all files should get the same result
 
183
        self.assertEqual(st1.st_mtime, st2.st_mtime)
 
184
 
 
185
    def test_change_root_id(self):
 
186
        transform, root = self.get_transform()
 
187
        self.assertNotEqual('new-root-id', self.wt.get_root_id())
 
188
        transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
189
        transform.delete_contents(root)
 
190
        transform.unversion_file(root)
 
191
        transform.fixup_new_roots()
 
192
        transform.apply()
 
193
        self.assertEqual('new-root-id', self.wt.get_root_id())
 
194
 
 
195
    def test_change_root_id_add_files(self):
 
196
        transform, root = self.get_transform()
 
197
        self.assertNotEqual('new-root-id', self.wt.get_root_id())
 
198
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
199
        transform.new_file('file', new_trans_id, ['new-contents\n'],
 
200
                           'new-file-id')
 
201
        transform.delete_contents(root)
 
202
        transform.unversion_file(root)
 
203
        transform.fixup_new_roots()
 
204
        transform.apply()
 
205
        self.assertEqual('new-root-id', self.wt.get_root_id())
 
206
        self.assertEqual('new-file-id', self.wt.path2id('file'))
 
207
        self.assertFileEqual('new-contents\n', self.wt.abspath('file'))
 
208
 
 
209
    def test_add_two_roots(self):
 
210
        transform, root = self.get_transform()
 
211
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'new-root-id')
 
212
        new_trans_id = transform.new_directory('', ROOT_PARENT, 'alt-root-id')
 
213
        self.assertRaises(ValueError, transform.fixup_new_roots)
 
214
 
139
215
    def test_hardlink(self):
140
216
        self.requireFeature(HardlinkFeature)
141
217
        transform, root = self.get_transform()
369
445
        self.assertContainsRe(transform._limbo_name(first), 'new-1/file')
370
446
        self.assertNotContainsRe(transform._limbo_name(second), 'new-1/FiLe')
371
447
 
 
448
    def test_adjust_path_updates_child_limbo_names(self):
 
449
        tree = self.make_branch_and_tree('tree')
 
450
        transform = TreeTransform(tree)
 
451
        self.addCleanup(transform.finalize)
 
452
        foo_id = transform.new_directory('foo', transform.root)
 
453
        bar_id = transform.new_directory('bar', foo_id)
 
454
        baz_id = transform.new_directory('baz', bar_id)
 
455
        qux_id = transform.new_directory('qux', baz_id)
 
456
        transform.adjust_path('quxx', foo_id, bar_id)
 
457
        self.assertStartsWith(transform._limbo_name(qux_id),
 
458
                              transform._limbo_name(bar_id))
 
459
 
372
460
    def test_add_del(self):
373
461
        start, root = self.get_transform()
374
462
        start.new_directory('a', root, 'a')
525
613
        resolve_conflicts(replace)
526
614
        replace.apply()
527
615
 
528
 
    def test_symlinks(self):
 
616
    def _test_symlinks(self, link_name1,link_target1,
 
617
                       link_name2, link_target2):
 
618
 
 
619
        def ozpath(p): return 'oz/' + p
 
620
 
529
621
        self.requireFeature(SymlinkFeature)
530
 
        transform,root = self.get_transform()
 
622
        transform, root = self.get_transform()
531
623
        oz_id = transform.new_directory('oz', root, 'oz-id')
532
 
        wizard = transform.new_symlink('wizard', oz_id, 'wizard-target',
 
624
        wizard = transform.new_symlink(link_name1, oz_id, link_target1,
533
625
                                       'wizard-id')
534
 
        wiz_id = transform.create_path('wizard2', oz_id)
535
 
        transform.create_symlink('behind_curtain', wiz_id)
 
626
        wiz_id = transform.create_path(link_name2, oz_id)
 
627
        transform.create_symlink(link_target2, wiz_id)
536
628
        transform.version_file('wiz-id2', wiz_id)
537
629
        transform.set_executability(True, wiz_id)
538
630
        self.assertEqual(transform.find_conflicts(),
539
631
                         [('non-file executability', wiz_id)])
540
632
        transform.set_executability(None, wiz_id)
541
633
        transform.apply()
542
 
        self.assertEqual(self.wt.path2id('oz/wizard'), 'wizard-id')
543
 
        self.assertEqual(file_kind(self.wt.abspath('oz/wizard')), 'symlink')
544
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard2')),
545
 
                         'behind_curtain')
546
 
        self.assertEqual(os.readlink(self.wt.abspath('oz/wizard')),
547
 
                         'wizard-target')
 
634
        self.assertEqual(self.wt.path2id(ozpath(link_name1)), 'wizard-id')
 
635
        self.assertEqual('symlink',
 
636
                         file_kind(self.wt.abspath(ozpath(link_name1))))
 
637
        self.assertEqual(link_target2,
 
638
                         osutils.readlink(self.wt.abspath(ozpath(link_name2))))
 
639
        self.assertEqual(link_target1,
 
640
                         osutils.readlink(self.wt.abspath(ozpath(link_name1))))
 
641
 
 
642
    def test_symlinks(self):
 
643
        self._test_symlinks('wizard', 'wizard-target',
 
644
                            'wizard2', 'behind_curtain')
 
645
 
 
646
    def test_symlinks_unicode(self):
 
647
        self.requireFeature(tests.UnicodeFilenameFeature)
 
648
        self._test_symlinks(u'\N{Euro Sign}wizard',
 
649
                            u'wizard-targ\N{Euro Sign}t',
 
650
                            u'\N{Euro Sign}wizard2',
 
651
                            u'b\N{Euro Sign}hind_curtain')
548
652
 
549
653
    def test_unable_create_symlink(self):
550
654
        def tt_helper():
653
757
                                         ' versioned, but has versioned'
654
758
                                         ' children.  Versioned directory.')
655
759
        self.assertEqual(conflicts_s[6], 'Conflict moving oz/emeraldcity into'
656
 
                                         ' oz/emeraldcity.  Cancelled move.')
 
760
                                         ' oz/emeraldcity. Cancelled move.')
657
761
 
658
762
    def prepare_wrong_parent_kind(self):
659
763
        tt, root = self.get_transform()
730
834
        create.apply()
731
835
        transform, root = self.get_transform()
732
836
        transform.adjust_root_path('oldroot', fun)
733
 
        new_root=transform.trans_id_tree_path('')
 
837
        new_root = transform.trans_id_tree_path('')
734
838
        transform.version_file('new-root', new_root)
735
839
        transform.apply()
736
840
 
748
852
        rename.set_executability(True, myfile)
749
853
        rename.apply()
750
854
 
 
855
    def test_rename_fails(self):
 
856
        # see https://bugs.launchpad.net/bzr/+bug/491763
 
857
        create, root_id = self.get_transform()
 
858
        first_dir = create.new_directory('first-dir', root_id, 'first-id')
 
859
        myfile = create.new_file('myfile', root_id, 'myfile-text',
 
860
                                 'myfile-id')
 
861
        create.apply()
 
862
        if os.name == "posix" and sys.platform != "cygwin":
 
863
            # posix filesystems fail on renaming if the readonly bit is set
 
864
            osutils.make_readonly(self.wt.abspath('first-dir'))
 
865
        elif os.name == "nt":
 
866
            # windows filesystems fail on renaming open files
 
867
            self.addCleanup(file(self.wt.abspath('myfile')).close)
 
868
        else:
 
869
            self.skip("Don't know how to force a permissions error on rename")
 
870
        # now transform to rename
 
871
        rename_transform, root_id = self.get_transform()
 
872
        file_trans_id = rename_transform.trans_id_file_id('myfile-id')
 
873
        dir_id = rename_transform.trans_id_file_id('first-id')
 
874
        rename_transform.adjust_path('newname', dir_id, file_trans_id)
 
875
        e = self.assertRaises(errors.TransformRenameFailed,
 
876
            rename_transform.apply)
 
877
        # On nix looks like: 
 
878
        # "Failed to rename .../work/.bzr/checkout/limbo/new-1
 
879
        # to .../first-dir/newname: [Errno 13] Permission denied"
 
880
        # On windows looks like:
 
881
        # "Failed to rename .../work/myfile to 
 
882
        # .../work/.bzr/checkout/limbo/new-1: [Errno 13] Permission denied"
 
883
        # The strerror will vary per OS and language so it's not checked here
 
884
        self.assertContainsRe(str(e),
 
885
            "Failed to rename .*(first-dir.newname:|myfile)")
 
886
 
751
887
    def test_set_executability_order(self):
752
888
        """Ensure that executability behaves the same, no matter what order.
753
889
 
1840
1976
        self.assertEqual([], list(target.iter_changes(revision_tree)))
1841
1977
        self.assertTrue(source.is_executable('file1-id'))
1842
1978
 
 
1979
    def install_rot13_content_filter(self, pattern):
 
1980
        # We could use
 
1981
        # self.addCleanup(filters._reset_registry, filters._reset_registry())
 
1982
        # below, but that looks a bit... hard to read even if it's exactly
 
1983
        # the same thing.
 
1984
        original_registry = filters._reset_registry()
 
1985
        def restore_registry():
 
1986
            filters._reset_registry(original_registry)
 
1987
        self.addCleanup(restore_registry)
 
1988
        def rot13(chunks, context=None):
 
1989
            return [''.join(chunks).encode('rot13')]
 
1990
        rot13filter = filters.ContentFilter(rot13, rot13)
 
1991
        filters.register_filter_stack_map('rot13', {'yes': [rot13filter]}.get)
 
1992
        os.mkdir(self.test_home_dir + '/.bazaar')
 
1993
        rules_filename = self.test_home_dir + '/.bazaar/rules'
 
1994
        f = open(rules_filename, 'wb')
 
1995
        f.write('[name %s]\nrot13=yes\n' % (pattern,))
 
1996
        f.close()
 
1997
        def uninstall_rules():
 
1998
            os.remove(rules_filename)
 
1999
            rules.reset_rules()
 
2000
        self.addCleanup(uninstall_rules)
 
2001
        rules.reset_rules()
 
2002
 
 
2003
    def test_build_tree_content_filtered_files_are_not_hardlinked(self):
 
2004
        """build_tree will not hardlink files that have content filtering rules
 
2005
        applied to them (but will still hardlink other files from the same tree
 
2006
        if it can).
 
2007
        """
 
2008
        self.requireFeature(HardlinkFeature)
 
2009
        self.install_rot13_content_filter('file1')
 
2010
        source = self.create_ab_tree()
 
2011
        target = self.make_branch_and_tree('target')
 
2012
        revision_tree = source.basis_tree()
 
2013
        revision_tree.lock_read()
 
2014
        self.addCleanup(revision_tree.unlock)
 
2015
        build_tree(revision_tree, target, source, hardlink=True)
 
2016
        target.lock_read()
 
2017
        self.addCleanup(target.unlock)
 
2018
        self.assertEqual([], list(target.iter_changes(revision_tree)))
 
2019
        source_stat = os.stat('source/file1')
 
2020
        target_stat = os.stat('target/file1')
 
2021
        self.assertNotEqual(source_stat, target_stat)
 
2022
        source_stat = os.stat('source/file2')
 
2023
        target_stat = os.stat('target/file2')
 
2024
        self.assertEqualStat(source_stat, target_stat)
 
2025
 
1843
2026
    def test_case_insensitive_build_tree_inventory(self):
 
2027
        if (tests.CaseInsensitiveFilesystemFeature.available()
 
2028
            or tests.CaseInsCasePresFilenameFeature.available()):
 
2029
            raise tests.UnavailableFeature('Fully case sensitive filesystem')
1844
2030
        source = self.make_branch_and_tree('source')
1845
2031
        self.build_tree(['source/file', 'source/FILE'])
1846
2032
        source.add(['file', 'FILE'], ['lower-id', 'upper-id'])
1854
2040
        self.assertEqual('FILE', target.id2path('upper-id'))
1855
2041
 
1856
2042
 
 
2043
class TestCommitTransform(tests.TestCaseWithTransport):
 
2044
 
 
2045
    def get_branch(self):
 
2046
        tree = self.make_branch_and_tree('tree')
 
2047
        tree.lock_write()
 
2048
        self.addCleanup(tree.unlock)
 
2049
        tree.commit('empty commit')
 
2050
        return tree.branch
 
2051
 
 
2052
    def get_branch_and_transform(self):
 
2053
        branch = self.get_branch()
 
2054
        tt = TransformPreview(branch.basis_tree())
 
2055
        self.addCleanup(tt.finalize)
 
2056
        return branch, tt
 
2057
 
 
2058
    def test_commit_wrong_basis(self):
 
2059
        branch = self.get_branch()
 
2060
        basis = branch.repository.revision_tree(
 
2061
            _mod_revision.NULL_REVISION)
 
2062
        tt = TransformPreview(basis)
 
2063
        self.addCleanup(tt.finalize)
 
2064
        e = self.assertRaises(ValueError, tt.commit, branch, '')
 
2065
        self.assertEqual('TreeTransform not based on branch basis: null:',
 
2066
                         str(e))
 
2067
 
 
2068
    def test_empy_commit(self):
 
2069
        branch, tt = self.get_branch_and_transform()
 
2070
        rev = tt.commit(branch, 'my message')
 
2071
        self.assertEqual(2, branch.revno())
 
2072
        repo = branch.repository
 
2073
        self.assertEqual('my message', repo.get_revision(rev).message)
 
2074
 
 
2075
    def test_merge_parents(self):
 
2076
        branch, tt = self.get_branch_and_transform()
 
2077
        rev = tt.commit(branch, 'my message', ['rev1b', 'rev1c'])
 
2078
        self.assertEqual(['rev1b', 'rev1c'],
 
2079
                         branch.basis_tree().get_parent_ids()[1:])
 
2080
 
 
2081
    def test_first_commit(self):
 
2082
        branch = self.make_branch('branch')
 
2083
        branch.lock_write()
 
2084
        self.addCleanup(branch.unlock)
 
2085
        tt = TransformPreview(branch.basis_tree())
 
2086
        self.addCleanup(tt.finalize)
 
2087
        tt.new_directory('', ROOT_PARENT, 'TREE_ROOT')
 
2088
        rev = tt.commit(branch, 'my message')
 
2089
        self.assertEqual([], branch.basis_tree().get_parent_ids())
 
2090
        self.assertNotEqual(_mod_revision.NULL_REVISION,
 
2091
                            branch.last_revision())
 
2092
 
 
2093
    def test_first_commit_with_merge_parents(self):
 
2094
        branch = self.make_branch('branch')
 
2095
        branch.lock_write()
 
2096
        self.addCleanup(branch.unlock)
 
2097
        tt = TransformPreview(branch.basis_tree())
 
2098
        self.addCleanup(tt.finalize)
 
2099
        e = self.assertRaises(ValueError, tt.commit, branch,
 
2100
                          'my message', ['rev1b-id'])
 
2101
        self.assertEqual('Cannot supply merge parents for first commit.',
 
2102
                         str(e))
 
2103
        self.assertEqual(_mod_revision.NULL_REVISION, branch.last_revision())
 
2104
 
 
2105
    def test_add_files(self):
 
2106
        branch, tt = self.get_branch_and_transform()
 
2107
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
2108
        trans_id = tt.new_directory('dir', tt.root, 'dir-id')
 
2109
        if SymlinkFeature.available():
 
2110
            tt.new_symlink('symlink', trans_id, 'target', 'symlink-id')
 
2111
        rev = tt.commit(branch, 'message')
 
2112
        tree = branch.basis_tree()
 
2113
        self.assertEqual('file', tree.id2path('file-id'))
 
2114
        self.assertEqual('contents', tree.get_file_text('file-id'))
 
2115
        self.assertEqual('dir', tree.id2path('dir-id'))
 
2116
        if SymlinkFeature.available():
 
2117
            self.assertEqual('dir/symlink', tree.id2path('symlink-id'))
 
2118
            self.assertEqual('target', tree.get_symlink_target('symlink-id'))
 
2119
 
 
2120
    def test_add_unversioned(self):
 
2121
        branch, tt = self.get_branch_and_transform()
 
2122
        tt.new_file('file', tt.root, 'contents')
 
2123
        self.assertRaises(errors.StrictCommitFailed, tt.commit, branch,
 
2124
                          'message', strict=True)
 
2125
 
 
2126
    def test_modify_strict(self):
 
2127
        branch, tt = self.get_branch_and_transform()
 
2128
        tt.new_file('file', tt.root, 'contents', 'file-id')
 
2129
        tt.commit(branch, 'message', strict=True)
 
2130
        tt = TransformPreview(branch.basis_tree())
 
2131
        self.addCleanup(tt.finalize)
 
2132
        trans_id = tt.trans_id_file_id('file-id')
 
2133
        tt.delete_contents(trans_id)
 
2134
        tt.create_file('contents', trans_id)
 
2135
        tt.commit(branch, 'message', strict=True)
 
2136
 
 
2137
    def test_commit_malformed(self):
 
2138
        """Committing a malformed transform should raise an exception.
 
2139
 
 
2140
        In this case, we are adding a file without adding its parent.
 
2141
        """
 
2142
        branch, tt = self.get_branch_and_transform()
 
2143
        parent_id = tt.trans_id_file_id('parent-id')
 
2144
        tt.new_file('file', parent_id, 'contents', 'file-id')
 
2145
        self.assertRaises(errors.MalformedTransform, tt.commit, branch,
 
2146
                          'message')
 
2147
 
 
2148
    def test_commit_rich_revision_data(self):
 
2149
        branch, tt = self.get_branch_and_transform()
 
2150
        rev_id = tt.commit(branch, 'message', timestamp=1, timezone=43201,
 
2151
                           committer='me <me@example.com>',
 
2152
                           revprops={'foo': 'bar'}, revision_id='revid-1',
 
2153
                           authors=['Author1 <author1@example.com>',
 
2154
                              'Author2 <author2@example.com>',
 
2155
                               ])
 
2156
        self.assertEqual('revid-1', rev_id)
 
2157
        revision = branch.repository.get_revision(rev_id)
 
2158
        self.assertEqual(1, revision.timestamp)
 
2159
        self.assertEqual(43201, revision.timezone)
 
2160
        self.assertEqual('me <me@example.com>', revision.committer)
 
2161
        self.assertEqual(['Author1 <author1@example.com>',
 
2162
                          'Author2 <author2@example.com>'],
 
2163
                         revision.get_apparent_authors())
 
2164
        del revision.properties['authors']
 
2165
        self.assertEqual({'foo': 'bar',
 
2166
                          'branch-nick': 'tree'},
 
2167
                         revision.properties)
 
2168
 
 
2169
    def test_no_explicit_revprops(self):
 
2170
        branch, tt = self.get_branch_and_transform()
 
2171
        rev_id = tt.commit(branch, 'message', authors=[
 
2172
            'Author1 <author1@example.com>',
 
2173
            'Author2 <author2@example.com>', ])
 
2174
        revision = branch.repository.get_revision(rev_id)
 
2175
        self.assertEqual(['Author1 <author1@example.com>',
 
2176
                          'Author2 <author2@example.com>'],
 
2177
                         revision.get_apparent_authors())
 
2178
        self.assertEqual('tree', revision.properties['branch-nick'])
 
2179
 
 
2180
 
1857
2181
class MockTransform(object):
1858
2182
 
1859
2183
    def has_named_child(self, by_parent, parent_id, name):
2026
2350
    def create_tree(self):
2027
2351
        tree = self.make_branch_and_tree('.')
2028
2352
        self.build_tree_contents([('a', 'content 1')])
 
2353
        tree.set_root_id('TREE_ROOT')
2029
2354
        tree.add('a', 'a-id')
2030
2355
        tree.commit('rev1', rev_id='rev1')
2031
2356
        return tree.branch.repository.revision_tree('rev1')
2132
2457
    def test_ignore_pb(self):
2133
2458
        # pb could be supported, but TT.iter_changes doesn't support it.
2134
2459
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
2135
 
        preview_tree.iter_changes(revision_tree, pb=progress.DummyProgress())
 
2460
        preview_tree.iter_changes(revision_tree)
2136
2461
 
2137
2462
    def test_kind(self):
2138
2463
        revision_tree = self.create_tree()
2153
2478
        self.assertEqual(os.stat(limbo_path).st_mtime,
2154
2479
                         preview_tree.get_file_mtime('file-id'))
2155
2480
 
 
2481
    def test_get_file_mtime_renamed(self):
 
2482
        work_tree = self.make_branch_and_tree('tree')
 
2483
        self.build_tree(['tree/file'])
 
2484
        work_tree.add('file', 'file-id')
 
2485
        preview = TransformPreview(work_tree)
 
2486
        self.addCleanup(preview.finalize)
 
2487
        file_trans_id = preview.trans_id_tree_file_id('file-id')
 
2488
        preview.adjust_path('renamed', preview.root, file_trans_id)
 
2489
        preview_tree = preview.get_preview_tree()
 
2490
        preview_mtime = preview_tree.get_file_mtime('file-id', 'renamed')
 
2491
        work_mtime = work_tree.get_file_mtime('file-id', 'file')
 
2492
 
2156
2493
    def test_get_file(self):
2157
2494
        preview = self.get_empty_preview()
2158
2495
        preview.new_file('file', preview.root, 'contents', 'file-id')
2306
2643
        self.assertEqual(('missing', None, None, None), summary)
2307
2644
 
2308
2645
    def test_file_content_summary_executable(self):
2309
 
        if not osutils.supports_executable():
2310
 
            raise TestNotApplicable()
2311
2646
        preview = self.get_empty_preview()
2312
2647
        path_id = preview.new_file('path', preview.root, 'contents', 'path-id')
2313
2648
        preview.set_executability(True, path_id)
2322
2657
        self.assertIs(None, summary[3])
2323
2658
 
2324
2659
    def test_change_executability(self):
2325
 
        if not osutils.supports_executable():
2326
 
            raise TestNotApplicable()
2327
2660
        tree = self.make_branch_and_tree('tree')
2328
2661
        self.build_tree(['tree/path'])
2329
2662
        tree.add('path')
2343
2676
        # size must be known
2344
2677
        self.assertEqual(len('contents'), summary[1])
2345
2678
        # not executable
2346
 
        if osutils.supports_executable():
2347
 
            self.assertEqual(False, summary[2])
2348
 
        else:
2349
 
            self.assertEqual(None, summary[2])
 
2679
        self.assertEqual(False, summary[2])
2350
2680
        # will not have hash (not cheap to determine)
2351
2681
        self.assertIs(None, summary[3])
2352
2682
 
2493
2823
 
2494
2824
    def test_walkdirs(self):
2495
2825
        preview = self.get_empty_preview()
2496
 
        preview.version_file('tree-root', preview.root)
 
2826
        root = preview.new_directory('', ROOT_PARENT, 'tree-root')
 
2827
        # FIXME: new_directory should mark root.
 
2828
        preview.fixup_new_roots()
2497
2829
        preview_tree = preview.get_preview_tree()
2498
2830
        file_trans_id = preview.new_file('a', preview.root, 'contents',
2499
2831
                                         'a-id')
2530
2862
        self.addCleanup(work_tree.unlock)
2531
2863
        preview = TransformPreview(work_tree)
2532
2864
        self.addCleanup(preview.finalize)
2533
 
        preview_tree = preview.get_preview_tree()
2534
2865
        file_trans_id = preview.trans_id_file_id('file-id')
2535
2866
        preview.delete_contents(file_trans_id)
2536
2867
        preview.create_file('a\nb\n', file_trans_id)
2537
 
        pb = progress.DummyProgress()
2538
 
        merger = Merger.from_revision_ids(pb, preview_tree,
 
2868
        preview_tree = preview.get_preview_tree()
 
2869
        merger = Merger.from_revision_ids(None, preview_tree,
2539
2870
                                          child_tree.branch.last_revision(),
2540
2871
                                          other_branch=child_tree.branch,
2541
2872
                                          tree_branch=work_tree.branch)
2547
2878
 
2548
2879
    def test_merge_preview_into_workingtree(self):
2549
2880
        tree = self.make_branch_and_tree('tree')
 
2881
        tree.set_root_id('TREE_ROOT')
2550
2882
        tt = TransformPreview(tree)
2551
2883
        self.addCleanup(tt.finalize)
2552
2884
        tt.new_file('name', tt.root, 'content', 'file-id')
2553
2885
        tree2 = self.make_branch_and_tree('tree2')
2554
 
        pb = progress.DummyProgress()
 
2886
        tree2.set_root_id('TREE_ROOT')
2555
2887
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2556
 
                                         pb, tree.basis_tree())
 
2888
                                         None, tree.basis_tree())
2557
2889
        merger.merge_type = Merge3Merger
2558
2890
        merger.do_merge()
2559
2891
 
2569
2901
        tt.create_file('baz', trans_id)
2570
2902
        tree2 = tree.bzrdir.sprout('tree2').open_workingtree()
2571
2903
        self.build_tree_contents([('tree2/foo', 'qux')])
2572
 
        pb = progress.DummyProgress()
 
2904
        pb = None
2573
2905
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
2574
2906
                                         pb, tree.basis_tree())
2575
2907
        merger.merge_type = Merge3Merger
2585
2917
                                                           'tree/foo'))
2586
2918
        self.assertEqual(False, preview_tree.is_executable('baz-id'))
2587
2919
 
 
2920
    def test_commit_preview_tree(self):
 
2921
        tree = self.make_branch_and_tree('tree')
 
2922
        rev_id = tree.commit('rev1')
 
2923
        tree.branch.lock_write()
 
2924
        self.addCleanup(tree.branch.unlock)
 
2925
        tt = TransformPreview(tree)
 
2926
        tt.new_file('file', tt.root, 'contents', 'file_id')
 
2927
        self.addCleanup(tt.finalize)
 
2928
        preview = tt.get_preview_tree()
 
2929
        preview.set_parent_ids([rev_id])
 
2930
        builder = tree.branch.get_commit_builder([rev_id])
 
2931
        list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
 
2932
        builder.finish_inventory()
 
2933
        rev2_id = builder.commit('rev2')
 
2934
        rev2_tree = tree.branch.repository.revision_tree(rev2_id)
 
2935
        self.assertEqual('contents', rev2_tree.get_file_text('file_id'))
 
2936
 
 
2937
    def test_ascii_limbo_paths(self):
 
2938
        self.requireFeature(tests.UnicodeFilenameFeature)
 
2939
        branch = self.make_branch('any')
 
2940
        tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
 
2941
        tt = TransformPreview(tree)
 
2942
        self.addCleanup(tt.finalize)
 
2943
        foo_id = tt.new_directory('', ROOT_PARENT)
 
2944
        bar_id = tt.new_file(u'\u1234bar', foo_id, 'contents')
 
2945
        limbo_path = tt._limbo_name(bar_id)
 
2946
        self.assertEqual(limbo_path.encode('ascii', 'replace'), limbo_path)
 
2947
 
2588
2948
 
2589
2949
class FakeSerializer(object):
2590
2950
    """Serializer implementation that simply returns the input.
2685
3045
        self.assertSerializesTo(self.symlink_creation_records(), tt)
2686
3046
 
2687
3047
    def test_deserialize_symlink_creation(self):
 
3048
        self.requireFeature(tests.SymlinkFeature)
2688
3049
        tt = self.get_preview()
2689
3050
        tt.deserialize(iter(self.symlink_creation_records()))
2690
 
        # XXX readlink should be returning unicode, not utf-8
2691
 
        foo_content = os.readlink(tt._limbo_name('new-1')).decode('utf-8')
 
3051
        abspath = tt._limbo_name('new-1')
 
3052
        foo_content = osutils.readlink(abspath)
2692
3053
        self.assertEqual(u'bar\u1234', foo_content)
2693
3054
 
2694
3055
    def make_destruction_preview(self):