# Copyright (C) 2006-2012, 2016 Canonical Ltd
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA

import os
from io import BytesIO

from breezy.tests.matchers import MatchesTreeChanges
from breezy.tests.per_tree import TestCaseWithTree

from ... import revision as _mod_revision
from ... import tests, trace
from ...diff import show_diff_trees
from ...merge import Merge3Merger, Merger
from ...transform import ROOT_PARENT, resolve_conflicts
from ...tree import TreeChange, find_previous_path
from ..features import SymlinkFeature, UnicodeFilenameFeature


class TestTransformPreview(TestCaseWithTree):

    def create_tree(self):
        tree = self.make_branch_and_tree('.')
        self.build_tree_contents([('a', b'content 1')])
        tree.add('a')
        revid1 = tree.commit('rev1')
        return tree.branch.repository.revision_tree(revid1)

    def get_empty_preview(self):
        repository = self.make_repository('repo')
        tree = repository.revision_tree(_mod_revision.NULL_REVISION)
        preview = tree.preview_transform()
        self.addCleanup(preview.finalize)
        return preview

    def test_transform_preview(self):
        revision_tree = self.create_tree()
        preview = revision_tree.preview_transform()
        self.addCleanup(preview.finalize)

    def test_transform_preview_tree(self):
        revision_tree = self.create_tree()
        preview = revision_tree.preview_transform()
        self.addCleanup(preview.finalize)
        preview.get_preview_tree()

    def test_transform_new_file(self):
        revision_tree = self.create_tree()
        preview = revision_tree.preview_transform()
        self.addCleanup(preview.finalize)
        preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
        preview_tree = preview.get_preview_tree()
        self.assertEqual(preview_tree.kind('file2'), 'file')
        with preview_tree.get_file('file2') as f:
            self.assertEqual(f.read(), b'content B\n')

    def test_diff_preview_tree(self):
        revision_tree = self.create_tree()
        preview = revision_tree.preview_transform()
        self.addCleanup(preview.finalize)
        preview.new_file('file2', preview.root, [b'content B\n'], b'file2-id')
        preview_tree = preview.get_preview_tree()
        out = BytesIO()
        show_diff_trees(revision_tree, preview_tree, out)
        lines = out.getvalue().splitlines()
        self.assertEqual(lines[0], b"=== added file 'file2'")
        # 3 lines of diff administrivia
        self.assertEqual(lines[4], b"+content B")

    def test_unsupported_symlink_diff(self):
        self.requireFeature(SymlinkFeature(self.test_dir))
        tree = self.make_branch_and_tree('.')
        self.build_tree_contents([('a', 'content 1')])
        tree.add('a')
        os.symlink('a', 'foo')
        tree.add('foo')
        revid1 = tree.commit('rev1')
        revision_tree = tree.branch.repository.revision_tree(revid1)
        os_symlink = getattr(os, 'symlink', None)
        os.symlink = None
        try:
            preview = revision_tree.preview_transform()
            self.addCleanup(preview.finalize)
            preview.delete_versioned(preview.trans_id_tree_path('foo'))
            preview_tree = preview.get_preview_tree()
            out = BytesIO()
            log = BytesIO()
            trace.push_log_file(log)
            show_diff_trees(revision_tree, preview_tree, out)
            lines = out.getvalue().splitlines()
        finally:
            os.symlink = os_symlink
        self.assertContainsRe(
            log.getvalue(),
            b'Ignoring "foo" as symlinks are not supported on this filesystem')

    def test_transform_conflicts(self):
        revision_tree = self.create_tree()
        preview = revision_tree.preview_transform()
        self.addCleanup(preview.finalize)
        preview.new_file('a', preview.root, [b'content 2'])
        resolve_conflicts(preview)
        trans_id = preview.trans_id_tree_path('a')
        self.assertEqual('a.moved', preview.final_name(trans_id))

    def get_tree_and_preview_tree(self):
        revision_tree = self.create_tree()
        preview = revision_tree.preview_transform()
        self.addCleanup(preview.finalize)
        a_trans_id = preview.trans_id_tree_path('a')
        preview.delete_contents(a_trans_id)
        preview.create_file([b'b content'], a_trans_id)
        preview_tree = preview.get_preview_tree()
        return revision_tree, preview_tree

    def test_iter_changes(self):
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
        self.assertThat(
            preview_tree.iter_changes(revision_tree),
            MatchesTreeChanges(
                revision_tree, preview_tree,
                [(('a', 'a'), True, (True, True), ('a', 'a'), ('file', 'file'),
                  (False, False), False)]))

    def test_include_unchanged_succeeds(self):
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
        changes = preview_tree.iter_changes(revision_tree,
                                            include_unchanged=True)

        root_entry = TreeChange(
            ('', ''), False, (True, True),
            ('', ''), ('directory', 'directory'), (False, False), False)
        a_entry = TreeChange(
            ('a', 'a'), True, (True, True),
            ('a', 'a'), ('file', 'file'),
            (False, False), False)

        self.assertThat(changes, MatchesTreeChanges(revision_tree, preview_tree, [root_entry, a_entry]))

    def test_specific_files(self):
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
        changes = preview_tree.iter_changes(revision_tree,
                                            specific_files=[''])
        a_entry = (('a', 'a'), True, (True, True),
                   ('a', 'a'), ('file', 'file'),
                   (False, False), False)

        self.assertThat(changes, MatchesTreeChanges(revision_tree, preview_tree, [a_entry]))

    def test_want_unversioned(self):
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
        changes = preview_tree.iter_changes(revision_tree,
                                            want_unversioned=True)
        a_entry = TreeChange(
            ('a', 'a'), True, (True, True),
            ('a', 'a'), ('file', 'file'),
            (False, False), False)

        self.assertThat(changes, MatchesTreeChanges(revision_tree, preview_tree, [a_entry]))

    def test_ignore_extra_trees_no_specific_files(self):
        # extra_trees is harmless without specific_files, so we'll silently
        # accept it, even though we won't use it.
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
        preview_tree.iter_changes(revision_tree, extra_trees=[preview_tree])

    def test_ignore_require_versioned_no_specific_files(self):
        # require_versioned is meaningless without specific_files.
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
        preview_tree.iter_changes(revision_tree, require_versioned=False)

    def test_ignore_pb(self):
        # pb could be supported, but TT.iter_changes doesn't support it.
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
        preview_tree.iter_changes(revision_tree)

    def test_kind(self):
        revision_tree = self.create_tree()
        preview = revision_tree.preview_transform()
        self.addCleanup(preview.finalize)
        preview.new_file('file', preview.root, [b'contents'], b'file-id')
        preview.new_directory('directory', preview.root, b'dir-id')
        preview_tree = preview.get_preview_tree()
        self.assertEqual('file', preview_tree.kind('file'))
        self.assertEqual('directory', preview_tree.kind('directory'))

    def test_get_file_mtime(self):
        preview = self.get_empty_preview()
        file_trans_id = preview.new_file('file', preview.root, [b'contents'],
                                         b'file-id')
        limbo_path = preview._limbo_name(file_trans_id)
        preview_tree = preview.get_preview_tree()
        self.assertEqual(os.stat(limbo_path).st_mtime,
                         preview_tree.get_file_mtime('file'))

    def test_get_file_mtime_renamed(self):
        work_tree = self.make_branch_and_tree('tree')
        self.build_tree(['tree/file'])
        work_tree.add('file')
        preview = work_tree.preview_transform()
        self.addCleanup(preview.finalize)
        file_trans_id = preview.trans_id_tree_path('file')
        preview.adjust_path('renamed', preview.root, file_trans_id)
        preview_tree = preview.get_preview_tree()
        preview_mtime = preview_tree.get_file_mtime('renamed')
        work_mtime = work_tree.get_file_mtime('file')

    def test_get_file_size(self):
        work_tree = self.make_branch_and_tree('tree')
        self.build_tree_contents([('tree/old', b'old')])
        work_tree.add('old')
        preview = work_tree.preview_transform()
        self.addCleanup(preview.finalize)
        preview.new_file('name', preview.root, [b'contents'], b'new-id',
                         'executable')
        tree = preview.get_preview_tree()
        self.assertEqual(len('old'), tree.get_file_size('old'))
        self.assertEqual(len('contents'), tree.get_file_size('name'))

    def test_get_file(self):
        preview = self.get_empty_preview()
        preview.new_file('file', preview.root, [b'contents'], b'file-id')
        preview_tree = preview.get_preview_tree()
        with preview_tree.get_file('file') as tree_file:
            self.assertEqual(b'contents', tree_file.read())

    def test_get_symlink_target(self):
        self.requireFeature(SymlinkFeature(self.test_dir))
        preview = self.get_empty_preview()
        preview.new_symlink('symlink', preview.root, 'target', b'symlink-id')
        preview_tree = preview.get_preview_tree()
        self.assertEqual('target',
                         preview_tree.get_symlink_target('symlink'))

    def test_all_file_ids(self):
        if not self.workingtree_format.supports_setting_file_ids:
            raise tests.TestNotApplicable(
                'format does not support setting file ids')
        tree = self.make_branch_and_tree('tree')
        self.build_tree(['tree/a', 'tree/b', 'tree/c'])
        tree.add(['a', 'b', 'c'], ids=[b'a-id', b'b-id', b'c-id'])
        preview = tree.preview_transform()
        self.addCleanup(preview.finalize)
        preview.unversion_file(preview.trans_id_file_id(b'b-id'))
        c_trans_id = preview.trans_id_file_id(b'c-id')
        preview.unversion_file(c_trans_id)
        preview.version_file(c_trans_id, file_id=b'c-id')
        preview_tree = preview.get_preview_tree()
        self.assertEqual({b'a-id', b'c-id', tree.path2id('')},
                         preview_tree.all_file_ids())

    def test_path2id_deleted_unchanged(self):
        tree = self.make_branch_and_tree('tree')
        self.build_tree(['tree/unchanged', 'tree/deleted'])
        tree.add(['unchanged', 'deleted'])
        preview = tree.preview_transform()
        self.addCleanup(preview.finalize)
        preview.unversion_file(preview.trans_id_tree_path('deleted'))
        preview_tree = preview.get_preview_tree()
        self.assertEqual(
            'unchanged',
            find_previous_path(preview_tree, tree, 'unchanged'))
        self.assertFalse(preview_tree.is_versioned('deleted'))

    def test_path2id_created(self):
        tree = self.make_branch_and_tree('tree')
        self.build_tree(['tree/unchanged'])
        tree.add(['unchanged'])
        preview = tree.preview_transform()
        self.addCleanup(preview.finalize)
        preview.new_file('new', preview.trans_id_tree_path('unchanged'),
                         [b'contents'], b'new-id')
        preview_tree = preview.get_preview_tree()
        self.assertTrue(preview_tree.is_versioned('unchanged/new'))
        if self.workingtree_format.supports_setting_file_ids:
            self.assertEqual(b'new-id', preview_tree.path2id('unchanged/new'))

    def test_path2id_moved(self):
        tree = self.make_branch_and_tree('tree')
        self.build_tree(['tree/old_parent/', 'tree/old_parent/child'])
        tree.add(['old_parent', 'old_parent/child'])
        preview = tree.preview_transform()
        self.addCleanup(preview.finalize)
        new_parent = preview.new_directory('new_parent', preview.root,
                                           b'new_parent-id')
        preview.adjust_path('child', new_parent,
                            preview.trans_id_tree_path('old_parent/child'))
        preview_tree = preview.get_preview_tree()
        self.assertFalse(preview_tree.is_versioned('old_parent/child'))
        self.assertEqual(
            'new_parent/child',
            find_previous_path(tree, preview_tree, 'old_parent/child'))
        if self.workingtree_format.supports_setting_file_ids:
            self.assertEqual(
                tree.path2id('old_parent/child'),
                preview_tree.path2id('new_parent/child'))

    def test_path2id_renamed_parent(self):
        tree = self.make_branch_and_tree('tree')
        self.build_tree(['tree/old_name/', 'tree/old_name/child'])
        tree.add(['old_name', 'old_name/child'])
        preview = tree.preview_transform()
        self.addCleanup(preview.finalize)
        preview.adjust_path('new_name', preview.root,
                            preview.trans_id_tree_path('old_name'))
        preview_tree = preview.get_preview_tree()
        self.assertFalse(preview_tree.is_versioned('old_name/child'))
        self.assertEqual(
            'new_name/child',
            find_previous_path(tree, preview_tree, 'old_name/child'))
        if tree.supports_setting_file_ids():
            self.assertEqual(
                tree.path2id('old_name/child'),
                preview_tree.path2id('new_name/child'))

    def assertMatchingIterEntries(self, tt, specific_files=None):
        preview_tree = tt.get_preview_tree()
        preview_result = list(preview_tree.iter_entries_by_dir(
                              specific_files=specific_files))
        tree = tt._tree
        tt.apply()
        actual_result = list(tree.iter_entries_by_dir(
            specific_files=specific_files))
        self.assertEqual(actual_result, preview_result)

    def test_iter_entries_by_dir_new(self):
        tree = self.make_branch_and_tree('tree')
        tt = tree.transform()
        tt.new_file('new', tt.root, [b'contents'], b'new-id')
        self.assertMatchingIterEntries(tt)

    def test_iter_entries_by_dir_deleted(self):
        tree = self.make_branch_and_tree('tree')
        self.build_tree(['tree/deleted'])
        tree.add('deleted')
        tt = tree.transform()
        tt.delete_contents(tt.trans_id_tree_path('deleted'))
        self.assertMatchingIterEntries(tt)

    def test_iter_entries_by_dir_unversioned(self):
        tree = self.make_branch_and_tree('tree')
        self.build_tree(['tree/removed'])
        tree.add('removed')
        tt = tree.transform()
        tt.unversion_file(tt.trans_id_tree_path('removed'))
        self.assertMatchingIterEntries(tt)

    def test_iter_entries_by_dir_moved(self):
        tree = self.make_branch_and_tree('tree')
        self.build_tree(['tree/moved', 'tree/new_parent/'])
        tree.add(['moved', 'new_parent'])
        tt = tree.transform()
        tt.adjust_path(
            'moved', tt.trans_id_tree_path('new_parent'),
            tt.trans_id_tree_path('moved'))
        self.assertMatchingIterEntries(tt)

    def test_iter_entries_by_dir_specific_files(self):
        tree = self.make_branch_and_tree('tree')
        self.build_tree(['tree/parent/', 'tree/parent/child'])
        tree.add(['parent', 'parent/child'])
        tt = tree.transform()
        self.assertMatchingIterEntries(tt, ['', 'parent/child'])

    def test_symlink_content_summary(self):
        self.requireFeature(SymlinkFeature(self.test_dir))
        preview = self.get_empty_preview()
        preview.new_symlink('path', preview.root, 'target', b'path-id')
        summary = preview.get_preview_tree().path_content_summary('path')
        self.assertEqual(('symlink', None, None, 'target'), summary)

    def test_missing_content_summary(self):
        preview = self.get_empty_preview()
        summary = preview.get_preview_tree().path_content_summary('path')
        self.assertEqual(('missing', None, None, None), summary)

    def test_deleted_content_summary(self):
        tree = self.make_branch_and_tree('tree')
        self.build_tree(['tree/path/'])
        tree.add('path')
        preview = tree.preview_transform()
        self.addCleanup(preview.finalize)
        preview.delete_contents(preview.trans_id_tree_path('path'))
        summary = preview.get_preview_tree().path_content_summary('path')
        self.assertEqual(('missing', None, None, None), summary)

    def test_file_content_summary_executable(self):
        preview = self.get_empty_preview()
        path_id = preview.new_file('path', preview.root, [
                                   b'contents'], b'path-id')
        preview.set_executability(True, path_id)
        summary = preview.get_preview_tree().path_content_summary('path')
        self.assertEqual(4, len(summary))
        self.assertEqual('file', summary[0])
        # size must be known
        self.assertEqual(len('contents'), summary[1])
        # executable
        self.assertEqual(True, summary[2])
        # will not have hash (not cheap to determine)
        self.assertIs(None, summary[3])

    def test_change_executability(self):
        tree = self.make_branch_and_tree('tree')
        self.build_tree(['tree/path'])
        tree.add('path')
        preview = tree.preview_transform()
        self.addCleanup(preview.finalize)
        path_id = preview.trans_id_tree_path('path')
        preview.set_executability(True, path_id)
        summary = preview.get_preview_tree().path_content_summary('path')
        self.assertEqual(True, summary[2])

    def test_file_content_summary_non_exec(self):
        preview = self.get_empty_preview()
        preview.new_file('path', preview.root, [b'contents'], b'path-id')
        summary = preview.get_preview_tree().path_content_summary('path')
        self.assertEqual(4, len(summary))
        self.assertEqual('file', summary[0])
        # size must be known
        self.assertEqual(len('contents'), summary[1])
        # not executable
        self.assertEqual(False, summary[2])
        # will not have hash (not cheap to determine)
        self.assertIs(None, summary[3])

    def test_dir_content_summary(self):
        preview = self.get_empty_preview()
        preview.new_directory('path', preview.root, b'path-id')
        summary = preview.get_preview_tree().path_content_summary('path')
        self.assertEqual(('directory', None, None, None), summary)

    def test_tree_content_summary(self):
        preview = self.get_empty_preview()
        path = preview.new_directory('path', preview.root, b'path-id')
        preview.set_tree_reference(b'rev-1', path)
        summary = preview.get_preview_tree().path_content_summary('path')
        self.assertEqual(4, len(summary))
        self.assertEqual('tree-reference', summary[0])

    def test_annotate(self):
        tree = self.make_branch_and_tree('tree')
        self.build_tree_contents([('tree/file', b'a\n')])
        tree.add('file')
        revid1 = tree.commit('a')
        self.build_tree_contents([('tree/file', b'a\nb\n')])
        preview = tree.preview_transform()
        self.addCleanup(preview.finalize)
        file_trans_id = preview.trans_id_tree_path('file')
        preview.delete_contents(file_trans_id)
        preview.create_file([b'a\nb\nc\n'], file_trans_id)
        preview_tree = preview.get_preview_tree()
        expected = [
            (revid1, b'a\n'),
            (b'me:', b'b\n'),
            (b'me:', b'c\n'),
        ]
        annotation = preview_tree.annotate_iter(
            'file', default_revision=b'me:')
        self.assertEqual(expected, annotation)

    def test_annotate_missing(self):
        preview = self.get_empty_preview()
        preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
        preview_tree = preview.get_preview_tree()
        expected = [
            (b'me:', b'a\n'),
            (b'me:', b'b\n'),
            (b'me:', b'c\n'),
            ]
        annotation = preview_tree.annotate_iter(
            'file', default_revision=b'me:')
        self.assertEqual(expected, annotation)

    def test_annotate_rename(self):
        tree = self.make_branch_and_tree('tree')
        self.build_tree_contents([('tree/file', b'a\n')])
        tree.add('file')
        revid1 = tree.commit('a')
        preview = tree.preview_transform()
        self.addCleanup(preview.finalize)
        file_trans_id = preview.trans_id_tree_path('file')
        preview.adjust_path('newname', preview.root, file_trans_id)
        preview_tree = preview.get_preview_tree()
        expected = [
            (revid1, b'a\n'),
        ]
        annotation = preview_tree.annotate_iter(
            'newname', default_revision=b'me:')
        self.assertEqual(expected, annotation)
        annotation = preview_tree.annotate_iter(
            'file', default_revision=b'me:')
        self.assertIs(None, annotation)

    def test_annotate_deleted(self):
        tree = self.make_branch_and_tree('tree')
        self.build_tree_contents([('tree/file', b'a\n')])
        tree.add('file')
        tree.commit('a')
        self.build_tree_contents([('tree/file', b'a\nb\n')])
        preview = tree.preview_transform()
        self.addCleanup(preview.finalize)
        file_trans_id = preview.trans_id_tree_path('file')
        preview.delete_contents(file_trans_id)
        preview_tree = preview.get_preview_tree()
        annotation = preview_tree.annotate_iter(
            'file', default_revision=b'me:')
        self.assertIs(None, annotation)

    def test_stored_kind(self):
        preview = self.get_empty_preview()
        preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
        preview_tree = preview.get_preview_tree()
        self.assertEqual('file', preview_tree.stored_kind('file'))

    def test_is_executable(self):
        preview = self.get_empty_preview()
        trans_id = preview.new_file('file', preview.root, [b'a\nb\nc\n'], b'file-id')
        preview.set_executability(True, trans_id)
        preview_tree = preview.get_preview_tree()
        self.assertEqual(True, preview_tree.is_executable('file'))

    def test_get_set_parent_ids(self):
        revision_tree, preview_tree = self.get_tree_and_preview_tree()
        self.assertEqual([], preview_tree.get_parent_ids())
        preview_tree.set_parent_ids([revision_tree.get_revision_id()])
        self.assertEqual(
            [revision_tree.get_revision_id()],
            preview_tree.get_parent_ids())

    def test_plan_file_merge(self):
        work_a = self.make_branch_and_tree('wta')
        self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
        work_a.add('file')
        base_id = work_a.commit('base version')
        tree_b = work_a.controldir.sprout('wtb').open_workingtree()
        preview = work_a.preview_transform()
        self.addCleanup(preview.finalize)
        trans_id = preview.trans_id_tree_path('file')
        preview.delete_contents(trans_id)
        preview.create_file([b'b\nc\nd\ne\n'], trans_id)
        self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
        tree_a = preview.get_preview_tree()
        if not getattr(tree_a, 'plan_file_merge', None):
            self.skipTest('tree does not support file merge planning')
        tree_a.set_parent_ids([base_id])
        self.addCleanup(tree_b.lock_read().unlock)
        self.assertEqual([
            ('killed-a', b'a\n'),
            ('killed-b', b'b\n'),
            ('unchanged', b'c\n'),
            ('unchanged', b'd\n'),
            ('new-a', b'e\n'),
            ('new-b', b'f\n'),
        ], list(tree_a.plan_file_merge('file', tree_b)))

    def test_plan_file_merge_revision_tree(self):
        work_a = self.make_branch_and_tree('wta')
        self.build_tree_contents([('wta/file', b'a\nb\nc\nd\n')])
        work_a.add('file')
        base_id = work_a.commit('base version')
        tree_b = work_a.controldir.sprout('wtb').open_workingtree()
        preview = work_a.basis_tree().preview_transform()
        self.addCleanup(preview.finalize)
        trans_id = preview.trans_id_tree_path('file')
        preview.delete_contents(trans_id)
        preview.create_file([b'b\nc\nd\ne\n'], trans_id)
        self.build_tree_contents([('wtb/file', b'a\nc\nd\nf\n')])
        tree_a = preview.get_preview_tree()
        if not getattr(tree_a, 'plan_file_merge', None):
            self.skipTest('tree does not support file merge planning')
        tree_a.set_parent_ids([base_id])
        self.addCleanup(tree_b.lock_read().unlock)
        self.assertEqual([
            ('killed-a', b'a\n'),
            ('killed-b', b'b\n'),
            ('unchanged', b'c\n'),
            ('unchanged', b'd\n'),
            ('new-a', b'e\n'),
            ('new-b', b'f\n'),
        ], list(tree_a.plan_file_merge('file', tree_b)))

    def test_walkdirs(self):
        preview = self.get_empty_preview()
        preview.new_directory('', ROOT_PARENT, b'tree-root')
        # FIXME: new_directory should mark root.
        preview.fixup_new_roots()
        preview_tree = preview.get_preview_tree()
        preview.new_file('a', preview.root, [b'contents'], b'a-id')
        expected = [('', [('a', 'a', 'file', None, 'file')])]
        self.assertEqual(expected, list(preview_tree.walkdirs()))

    def test_extras(self):
        work_tree = self.make_branch_and_tree('tree')
        self.build_tree(['tree/removed-file', 'tree/existing-file',
                         'tree/not-removed-file'])
        work_tree.add(['removed-file', 'not-removed-file'])
        preview = work_tree.preview_transform()
        self.addCleanup(preview.finalize)
        preview.new_file('new-file', preview.root, [b'contents'])
        preview.new_file('new-versioned-file', preview.root, [b'contents'],
                         b'new-versioned-id')
        tree = preview.get_preview_tree()
        self.assertEqual({'existing-file'}, set(work_tree.extras()))
        preview.unversion_file(preview.trans_id_tree_path('removed-file'))
        self.assertEqual({'new-file', 'removed-file', 'existing-file'},
                         set(tree.extras()))

    def test_merge_into_preview(self):
        work_tree = self.make_branch_and_tree('tree')
        self.build_tree_contents([('tree/file', b'b\n')])
        work_tree.add('file')
        work_tree.commit('first commit')
        child_tree = work_tree.controldir.sprout('child').open_workingtree()
        self.build_tree_contents([('child/file', b'b\nc\n')])
        child_tree.commit('child commit')
        child_tree.lock_write()
        self.addCleanup(child_tree.unlock)
        work_tree.lock_write()
        self.addCleanup(work_tree.unlock)
        preview = work_tree.preview_transform()
        self.addCleanup(preview.finalize)
        file_trans_id = preview.trans_id_tree_path('file')
        preview.delete_contents(file_trans_id)
        preview.create_file([b'a\nb\n'], file_trans_id)
        preview_tree = preview.get_preview_tree()
        merger = Merger.from_revision_ids(preview_tree,
                                          child_tree.branch.last_revision(),
                                          other_branch=child_tree.branch,
                                          tree_branch=work_tree.branch)
        merger.merge_type = Merge3Merger
        tt = merger.make_merger().make_preview_transform()
        self.addCleanup(tt.finalize)
        final_tree = tt.get_preview_tree()
        self.assertEqual(
            b'a\nb\nc\n',
            final_tree.get_file_text('file'))

    def test_merge_preview_into_workingtree(self):
        tree = self.make_branch_and_tree('tree')
        if tree.supports_setting_file_ids():
            tree.set_root_id(b'TREE_ROOT')
        tt = tree.preview_transform()
        self.addCleanup(tt.finalize)
        tt.new_file('name', tt.root, [b'content'], b'file-id')
        tree2 = self.make_branch_and_tree('tree2')
        if tree.supports_setting_file_ids():
            tree2.set_root_id(b'TREE_ROOT')
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
                                         tree.basis_tree())
        merger.merge_type = Merge3Merger
        merger.do_merge()

    def test_merge_preview_into_workingtree_handles_conflicts(self):
        tree = self.make_branch_and_tree('tree')
        self.build_tree_contents([('tree/foo', b'bar')])
        tree.add('foo')
        tree.commit('foo')
        tt = tree.preview_transform()
        self.addCleanup(tt.finalize)
        trans_id = tt.trans_id_tree_path('foo')
        tt.delete_contents(trans_id)
        tt.create_file([b'baz'], trans_id)
        tree2 = tree.controldir.sprout('tree2').open_workingtree()
        self.build_tree_contents([('tree2/foo', b'qux')])
        merger = Merger.from_uncommitted(tree2, tt.get_preview_tree(),
                                         tree.basis_tree())
        merger.merge_type = Merge3Merger
        merger.do_merge()

    def test_has_filename(self):
        wt = self.make_branch_and_tree('tree')
        self.build_tree(['tree/unmodified', 'tree/removed', 'tree/modified'])
        tt = wt.preview_transform()
        removed_id = tt.trans_id_tree_path('removed')
        tt.delete_contents(removed_id)
        tt.new_file('new', tt.root, [b'contents'])
        modified_id = tt.trans_id_tree_path('modified')
        tt.delete_contents(modified_id)
        tt.create_file([b'modified-contents'], modified_id)
        self.addCleanup(tt.finalize)
        tree = tt.get_preview_tree()
        self.assertTrue(tree.has_filename('unmodified'))
        self.assertFalse(tree.has_filename('not-present'))
        self.assertFalse(tree.has_filename('removed'))
        self.assertTrue(tree.has_filename('new'))
        self.assertTrue(tree.has_filename('modified'))

    def test_is_executable2(self):
        tree = self.make_branch_and_tree('tree')
        preview = tree.preview_transform()
        self.addCleanup(preview.finalize)
        preview.new_file('foo', preview.root, [b'bar'], b'baz-id')
        preview_tree = preview.get_preview_tree()
        self.assertEqual(False, preview_tree.is_executable('tree/foo'))

    def test_commit_preview_tree(self):
        tree = self.make_branch_and_tree('tree')
        rev_id = tree.commit('rev1')
        tree.branch.lock_write()
        self.addCleanup(tree.branch.unlock)
        tt = tree.preview_transform()
        tt.new_file('file', tt.root, [b'contents'], b'file_id')
        self.addCleanup(tt.finalize)
        preview = tt.get_preview_tree()
        preview.set_parent_ids([rev_id])
        builder = tree.branch.get_commit_builder([rev_id])
        list(builder.record_iter_changes(preview, rev_id, tt.iter_changes()))
        builder.finish_inventory()
        rev2_id = builder.commit('rev2')
        rev2_tree = tree.branch.repository.revision_tree(rev2_id)
        self.assertEqual(b'contents', rev2_tree.get_file_text('file'))

    def test_ascii_limbo_paths(self):
        self.requireFeature(UnicodeFilenameFeature)
        branch = self.make_branch('any')
        tree = branch.repository.revision_tree(_mod_revision.NULL_REVISION)
        tt = tree.preview_transform()
        self.addCleanup(tt.finalize)
        foo_id = tt.new_directory('', ROOT_PARENT)
        bar_id = tt.new_file('\u1234bar', foo_id, [b'contents'])
        limbo_path = tt._limbo_name(bar_id)
        self.assertEqual(limbo_path, limbo_path)
