# Copyright (C) 2006, 2007, 2009, 2010 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

"""Tests of the WorkingTree.unversion API."""

from breezy import (
    errors,
    )
from breezy.tests.matchers import HasPathRelations
from breezy.tests.per_workingtree import TestCaseWithWorkingTree


class TestUnversion(TestCaseWithWorkingTree):

    def test_unversion_requires_write_lock(self):
        """WT.unversion([]) in a read lock raises ReadOnlyError."""
        tree = self.make_branch_and_tree('.')
        tree.lock_read()
        self.assertRaises(errors.ReadOnlyError, tree.unversion, [])
        tree.unlock()

    def test_unversion_missing_file(self):
        """WT.unversion(['missing']) raises NoSuchId."""
        tree = self.make_branch_and_tree('.')
        self.assertRaises(errors.NoSuchFile, tree.unversion, ['missing'])

    def test_unversion_parent_and_child_renamed_bug_187207(self):
        # When unversioning dirstate trees show a bug in dealing with
        # unversioning children of reparented children of unversioned
        # paths when relocation entries are present and the relocation
        # points later into the dirstate.
        tree = self.make_branch_and_tree('.')
        self.build_tree(['del/', 'del/sub/', 'del/sub/b'])
        tree.add(['del', 'del/sub', 'del/sub/b'])
        revid = tree.commit('setup')
        revtree = tree.branch.repository.revision_tree(revid)
        tree.rename_one('del/sub', 'sub')
        self.assertThat(
            tree,
            HasPathRelations(revtree, [('', ''), ('del/', 'del/'), ('sub/', 'del/sub/'), ('sub/b', 'del/sub/b')]))
        if tree.has_versioned_directories():
            tree.unversion(['del', 'sub/b'])
        else:
            tree.unversion(['sub/b'])
        self.assertThat(
            tree,
            HasPathRelations(revtree, [('', ''), ('sub/', 'del/sub/')]))

    def test_unversion_several_files(self):
        """After unversioning several files, they should not be versioned."""
        tree = self.make_branch_and_tree('.')
        self.build_tree(['a', 'b', 'c'])
        tree.add(['a', 'b', 'c'])
        # within a lock unversion should take effect
        tree.lock_write()
        self.assertTrue(tree.is_versioned('a'))
        tree.unversion(['a', 'b'])
        self.assertFalse(tree.is_versioned('a'))
        self.assertFalse(tree.is_versioned('a'))
        self.assertFalse(tree.is_versioned('b'))
        self.assertTrue(tree.is_versioned('c'))
        self.assertTrue(tree.has_filename('a'))
        self.assertTrue(tree.has_filename('b'))
        self.assertTrue(tree.has_filename('c'))
        tree.unlock()
        # the changes should have persisted to disk - reopen the workingtree
        # to be sure.
        tree = tree.controldir.open_workingtree()
        self.addCleanup(tree.lock_read().unlock)
        self.assertFalse(tree.is_versioned('a'))
        self.assertFalse(tree.is_versioned('b'))
        self.assertTrue(tree.is_versioned('c'))
        self.assertTrue(tree.has_filename('a'))
        self.assertTrue(tree.has_filename('b'))
        self.assertTrue(tree.has_filename('c'))

    def test_unversion_subtree(self):
        """Unversioning the root of a subtree unversions the entire subtree."""
        tree = self.make_branch_and_tree('.')
        self.build_tree(['a/', 'a/b', 'c'])
        tree.add(['a', 'a/b', 'c'])
        # within a lock unversion should take effect
        with tree.lock_write():
            tree.unversion(['a'])
            self.assertFalse(tree.is_versioned('a'))
            self.assertFalse(tree.is_versioned('a/b'))
            self.assertTrue(tree.is_versioned('c'))
            self.assertTrue(tree.has_filename('a'))
            self.assertTrue(tree.has_filename('a/b'))
            self.assertTrue(tree.has_filename('c'))

    def test_unversion_subtree_and_children(self):
        """Passing a child id will raise NoSuchId.

        This is because the parent directory will have already been removed.
        """
        tree = self.make_branch_and_tree('.')
        self.build_tree(['a/', 'a/b', 'a/c', 'd'])
        tree.add(['a', 'a/b', 'a/c', 'd'])
        with tree.lock_write():
            tree.unversion(['a/b', 'a'])
            self.assertFalse(tree.is_versioned('a'))
            self.assertFalse(tree.is_versioned('a/b'))
            self.assertFalse(tree.is_versioned('a/c'))
            self.assertTrue(tree.is_versioned('d'))
            # The files are still on disk
            self.assertTrue(tree.has_filename('a'))
            self.assertTrue(tree.has_filename('a/b'))
            self.assertTrue(tree.has_filename('a/c'))
            self.assertTrue(tree.has_filename('d'))

    def test_unversion_renamed(self):
        tree = self.make_branch_and_tree('a')
        self.build_tree(['a/dir/', 'a/dir/f1', 'a/dir/f2', 'a/dir/f3',
                         'a/dir2/'])
        tree.add(['dir', 'dir/f1', 'dir/f2', 'dir/f3', 'dir2'])
        rev_id1 = tree.commit('init')
        revtree = tree.branch.repository.revision_tree(rev_id1)
        # Start off by renaming entries, and then unversion a bunch of entries
        # https://bugs.launchpad.net/bzr/+bug/114615
        tree.rename_one('dir/f1', 'dir/a')
        tree.rename_one('dir/f2', 'dir/z')
        tree.move(['dir/f3'], 'dir2')

        self.assertThat(
            tree, HasPathRelations(
                revtree,
                [('', ''), ('dir/', 'dir/'), ('dir2/', 'dir2/'),
                 ('dir/a', 'dir/f1'), ('dir/z', 'dir/f2'),
                 ('dir2/f3', 'dir/f3')]))

        tree.unversion({'dir'})
        self.assertThat(
            tree,
            HasPathRelations(
                revtree,
                [('', ''), ('dir2/', 'dir2/'), ('dir2/f3', 'dir/f3')]))

    def test_unversion_after_conflicted_merge(self):
        # Test for bug #114615
        tree_a = self.make_branch_and_tree('A')
        self.build_tree(['A/a/', 'A/a/m', 'A/a/n'])
        tree_a.add(['a', 'a/m', 'a/n'])
        tree_a.commit('init')

        tree_b = tree_a.controldir.sprout('B').open_workingtree()
        self.build_tree(['B/xyz/'])
        tree_b.add(['xyz'])
        tree_b.rename_one('a/m', 'xyz/m')
        tree_b.unversion(['a'])
        tree_b.commit('delete in B')

        self.assertThat(
            tree_b,
            HasPathRelations(
                tree_a,
                [('', ''), ('xyz/', None), ('xyz/m', 'a/m')]))

        self.build_tree_contents([('A/a/n', b'new contents for n\n')])
        tree_a.commit('change n in A')

        # Merging from A should introduce conflicts because 'n' was modified
        # and removed, so 'a' needs to be restored. We also have a conflict
        # because 'a' is still an existing directory
        num_conflicts = tree_b.merge_from_branch(tree_a.branch)
        self.assertEqual(4, num_conflicts)

        self.assertThat(
            tree_b,
            HasPathRelations(
                tree_a,
                [('', ''), ('a/', 'a/'), ('xyz/', None),
                 ('a/n.OTHER', 'a/n'), ('xyz/m', 'a/m')]))

        tree_b.unversion(['a'])
        self.assertThat(
            tree_b,
            HasPathRelations(
                tree_a,
                [('', ''), ('xyz/', None), ('xyz/m', 'a/m')]))
