#    Copyright (C) 2011 Canonical Ltd
#    Copyright (C) 2019 Jelmer Vernooij <jelmer@jelmer.uk>
#
#    Breezy 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.
#
#    Breezy 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 Breezy; if not, write to the Free Software
#    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

"""Tests for the merge_quilt code."""

import os
import shutil

from .... import bedding, config, errors, trace
from ....merge import Merger
from ....mutabletree import MutableTree
from ....tests import TestCaseWithTransport, TestSkipped
from .. import (post_build_tree_quilt, post_merge_quilt_cleanup,
                pre_merge_quilt, start_commit_check_quilt)
from ..merge import tree_unapply_patches
from ..quilt import QuiltPatches
from . import quilt_feature

TRIVIAL_PATCH = """--- /dev/null	2012-01-02 01:09:10.986490031 +0100
+++ base/a	2012-01-02 20:03:59.710666215 +0100
@@ -0,0 +1 @@
+a
"""


def quilt_push_all(tree):
    QuiltPatches(tree, 'debian/patches').push_all()


class TestTreeUnapplyPatches(TestCaseWithTransport):

    _test_needs_features = [quilt_feature]

    def test_no_patches(self):
        tree = self.make_branch_and_tree('.')
        new_tree, target_dir = tree_unapply_patches(tree)
        self.assertIs(tree, new_tree)
        self.assertIs(None, target_dir)

    def test_unapply(self):
        orig_tree = self.make_branch_and_tree('source')
        self.build_tree(["source/debian/", "source/debian/patches/"])
        self.build_tree_contents([
            ("source/debian/patches/series", "patch1.diff\n"),
            ("source/debian/patches/patch1.diff", TRIVIAL_PATCH)])
        quilt_push_all(orig_tree)
        orig_tree.smart_add([orig_tree.basedir])
        tree, target_dir = tree_unapply_patches(orig_tree)
        self.addCleanup(shutil.rmtree, target_dir)
        self.assertPathExists("source/a")
        self.assertNotEqual(tree.basedir, orig_tree.basedir)
        self.assertPathDoesNotExist(tree.abspath("a"))
        self.assertPathExists(tree.abspath("debian/patches/series"))

    def test_unapply_nothing_applied(self):
        orig_tree = self.make_branch_and_tree('source')
        self.build_tree(["source/debian/", "source/debian/patches/"])
        self.build_tree_contents([
            ("source/debian/patches/series", "patch1.diff\n"),
            ("source/debian/patches/patch1.diff", TRIVIAL_PATCH)])
        orig_tree.smart_add([orig_tree.basedir])
        tree, target_dir = tree_unapply_patches(orig_tree)
        self.assertIs(tree, orig_tree)
        self.assertIs(None, target_dir)


class TestMergeHook(TestCaseWithTransport):

    _test_needs_features = [quilt_feature]

    def enable_hooks(self):
        Merger.hooks.install_named_hook(
            'pre_merge', pre_merge_quilt,
            'Debian quilt patch (un)applying and ancestry fixing')
        Merger.hooks.install_named_hook(
            'post_merge', post_merge_quilt_cleanup,
            'Cleaning up quilt temporary directories')
        MutableTree.hooks.install_named_hook(
            "post_build_tree", post_build_tree_quilt,
            "Apply quilt trees.")

    def test_diverged_patches(self):
        self.enable_hooks()

        tree_a = self.make_branch_and_tree('a')
        self.build_tree(
            ['a/debian/', 'a/debian/patches/', 'a/debian/source/', 'a/.pc/'])
        self.build_tree_contents([
            ('a/.pc/.quilt_patches', 'debian/patches\n'),
            ('a/.pc/.version', '2\n'),
            ('a/debian/source/format', '3.0 (quilt)'),
            ('a/debian/patches/series', 'patch1\n'),
            ('a/debian/patches/patch1', TRIVIAL_PATCH)])
        tree_a.smart_add([tree_a.basedir])
        tree_a.commit('initial')

        tree_b = tree_a.controldir.sprout('b').open_workingtree()
        self.build_tree_contents([
            ('a/debian/patches/patch1',
                "\n".join(TRIVIAL_PATCH.splitlines()[:-1] + ["+d\n"]))])
        quilt_push_all(tree_a)
        tree_a.smart_add([tree_a.basedir])
        tree_a.commit('apply patches')
        self.build_tree_contents([
            ('b/debian/patches/patch1',
                "\n".join(TRIVIAL_PATCH.splitlines()[:-1] + ["+c\n"]))])
        quilt_push_all(tree_b)
        tree_b.commit('apply patches')
        conflicts = tree_a.merge_from_branch(tree_b.branch)
        self.assertFileEqual("""\
--- /dev/null\t2012-01-02 01:09:10.986490031 +0100
+++ base/a\t2012-01-02 20:03:59.710666215 +0100
@@ -0,0 +1 @@
<<<<<<< TREE
+d
=======
+c
>>>>>>> MERGE-SOURCE
""", "a/debian/patches/patch1")
        # "a" should be unapplied again
        self.assertPathDoesNotExist("a/a")
        self.assertEqual(1, len(conflicts))

    def test_auto_apply_patches_after_checkout(self):
        self.enable_hooks()

        tree_a = self.make_branch_and_tree('a')

        self.build_tree(['a/debian/', 'a/debian/patches/'])
        self.build_tree_contents([
            ('a/debian/patches/series', 'patch1\n'),
            ('a/debian/patches/patch1', TRIVIAL_PATCH)])
        tree_a.smart_add([tree_a.basedir])
        tree_a.commit('initial')

        bedding.ensure_config_dir_exists()
        config.GlobalStack().set('quilt.tree_policy', 'applied')

        tree_a.branch.create_checkout("b")
        self.assertFileEqual("a\n", "b/a")

    def test_auto_apply_patches_after_update_format_1(self):
        self.enable_hooks()

        tree_a = self.make_branch_and_tree('a')
        tree_b = tree_a.branch.create_checkout("b")

        self.build_tree(['a/debian/', 'a/debian/patches/', 'a/.pc/'])
        self.build_tree_contents([
            ('a/.pc/.quilt_patches', 'debian/patches\n'),
            ('a/.pc/.version', '2\n'),
            ('a/debian/patches/series', 'patch1\n'),
            ('a/debian/patches/patch1', TRIVIAL_PATCH)])
        tree_a.smart_add([tree_a.basedir])
        tree_a.commit('initial')

        self.build_tree(["b/.bzr-builddeb/", "b/debian/", "b/debian/source/"])
        tree_b.get_config_stack().set('quilt.tree_policy', 'applied')
        self.build_tree_contents([
            ("b/debian/source/format", "1.0")])

        tree_b.update()
        self.assertFileEqual("a\n", "b/a")

    def test_auto_apply_patches_after_update(self):
        self.enable_hooks()

        tree_a = self.make_branch_and_tree('a')
        tree_b = tree_a.branch.create_checkout("b")

        self.build_tree(['a/debian/', 'a/debian/patches/', 'a/debian/source/', 'a/.pc/'])
        self.build_tree_contents([
            ('a/.pc/.quilt_patches', 'debian/patches\n'),
            ('a/.pc/.version', '2\n'),
            ('a/debian/source/format', '3.0 (quilt)'),
            ('a/debian/patches/series', 'patch1\n'),
            ('a/debian/patches/patch1', TRIVIAL_PATCH)])
        tree_a.smart_add([tree_a.basedir])
        tree_a.commit('initial')

        self.build_tree(["b/.bzr-builddeb/", "b/debian/", "b/debian/source/"])
        tree_b.get_config_stack().set('quilt.tree_policy', 'applied')
        self.build_tree_contents([
            ('b/debian/source/format', '3.0 (quilt)'),
            ])

        tree_b.update()
        self.assertFileEqual("a\n", "b/a")

    def test_auto_unapply_patches_after_update(self):
        self.enable_hooks()

        tree_a = self.make_branch_and_tree('a')
        tree_b = tree_a.branch.create_checkout("b")

        self.build_tree(['a/debian/', 'a/debian/patches/', 'a/debian/source/', 'a/.pc/'])
        self.build_tree_contents([
            ('a/.pc/.quilt_patches', 'debian/patches\n'),
            ('a/.pc/.version', '2\n'),
            ('a/debian/source/format', '3.0 (quilt)'),
            ('a/debian/patches/series', 'patch1\n'),
            ('a/debian/patches/patch1', TRIVIAL_PATCH)])
        tree_a.smart_add([tree_a.basedir])
        tree_a.commit('initial')

        self.build_tree(["b/.bzr-builddeb/"])
        tree_b.get_config_stack().set('quilt.tree_policy', 'unapplied')

        tree_b.update()
        self.assertPathDoesNotExist("b/a")

    def test_disabled_hook(self):
        self.enable_hooks()

        tree_a = self.make_branch_and_tree('a')
        tree_a.get_config_stack().set('quilt.smart_merge', False)
        self.build_tree(['a/debian/', 'a/debian/patches/', 'a/.pc/'])
        self.build_tree_contents([
            ('a/.pc/.quilt_patches', 'debian/patches\n'),
            ('a/.pc/.version', '2\n'),
            ('a/debian/patches/series', 'patch1\n'),
            ('a/debian/patches/patch1', TRIVIAL_PATCH),
            ("a/a", "")])
        tree_a.smart_add([tree_a.basedir])
        tree_a.commit('initial')

        tree_b = tree_a.controldir.sprout('b').open_workingtree()
        self.build_tree_contents([
            ('a/debian/patches/patch1',
                "\n".join(TRIVIAL_PATCH.splitlines()[:-1] + ["+d\n"]))])
        quilt_push_all(tree_a)
        tree_a.smart_add([tree_a.basedir])
        tree_a.commit('apply patches')
        self.assertFileEqual("d\n", "a/a")
        self.build_tree_contents([
            ('b/debian/patches/patch1',
                "\n".join(TRIVIAL_PATCH.splitlines()[:-1] + ["+c\n"]))])
        quilt_push_all(tree_b)
        tree_b.commit('apply patches')
        self.assertFileEqual("c\n", "b/a")
        conflicts = tree_a.merge_from_branch(tree_b.branch)
        self.assertFileEqual("""\
--- /dev/null\t2012-01-02 01:09:10.986490031 +0100
+++ base/a\t2012-01-02 20:03:59.710666215 +0100
@@ -0,0 +1 @@
<<<<<<< TREE
+d
=======
+c
>>>>>>> MERGE-SOURCE
""", "a/debian/patches/patch1")
        self.assertFileEqual("""\
<<<<<<< TREE
d
=======
c
>>>>>>> MERGE-SOURCE
""", "a/a")
        self.assertEqual(2, len(conflicts))



class StartCommitMergeHookTests(TestCaseWithTransport):

    _test_needs_features = [quilt_feature]

    def enable_hooks(self):
        MutableTree.hooks.install_named_hook(
            'start_commit', start_commit_check_quilt,
            'Check for (un)applied quilt patches')

    def test_applied(self):
        self.enable_hooks()
        tree = self.make_branch_and_tree('source')
        tree.get_config_stack().set('quilt.commit_policy', 'applied')
        self.build_tree(['source/debian/', 'source/debian/patches/',
                         'source/debian/source/'])
        self.build_tree_contents([
            ('source/debian/source/format', '3.0 (quilt)'),
            ('source/debian/patches/series', 'patch1\n'),
            ('source/debian/patches/patch1', TRIVIAL_PATCH)])
        self.assertPathDoesNotExist("source/.pc/applied-patches")
        self.assertPathDoesNotExist("source/a")
        tree.smart_add([tree.basedir])
        tree.commit("foo")
        self.assertPathExists("source/.pc/applied-patches")
        self.assertPathExists("source/a")

    def test_unapplied(self):
        self.enable_hooks()
        tree = self.make_branch_and_tree('source')
        tree.get_config_stack().set('quilt.commit_policy', 'unapplied')
        self.build_tree(
            ['source/debian/', 'source/debian/patches/',
             'source/debian/source/'])
        self.build_tree_contents([
            ('source/debian/patches/series', 'patch1\n'),
            ('source/debian/patches/patch1', TRIVIAL_PATCH),
            ('source/debian/source/format', '3.0 (quilt)')])
        quilt_push_all(tree)
        self.assertPathExists("source/.pc/applied-patches")
        self.assertPathExists("source/a")
        tree.smart_add([tree.basedir])
        tree.commit("foo")
        self.assertPathDoesNotExist("source/.pc/applied-patches")
        self.assertPathDoesNotExist("source/a")

    def test_warning(self):
        self.enable_hooks()
        warnings = []

        def warning(*args):
            if len(args) > 1:
                warnings.append(args[0] % args[1:])
            else:
                warnings.append(args[0])
        _warning = trace.warning
        trace.warning = warning
        self.addCleanup(setattr, trace, "warning", _warning)
        tree = self.make_branch_and_tree('source')
        self.build_tree(['source/debian/', 'source/debian/patches/',
                         'source/debian/source/'])
        self.build_tree_contents([
            ('source/debian/patches/series', 'patch1\n'),
            ('source/debian/patches/patch1', TRIVIAL_PATCH)])
        quilt_push_all(tree)
        tree.smart_add([tree.basedir])
        tree.commit("initial")
        self.assertEqual([], warnings)
        self.assertPathExists("source/.pc/applied-patches")
        self.assertPathExists("source/a")
        self.build_tree_contents([
            ('source/debian/source/format', '3.0 (quilt)'),
            ('source/debian/patches/series', 'patch1\npatch2\n'),
            ('source/debian/patches/patch2',
                """--- /dev/null	2012-01-02 01:09:10.986490031 +0100
+++ base/b	2012-01-02 20:03:59.710666215 +0100
@@ -0,0 +1 @@
+a
""")])
        tree.smart_add([tree.basedir])
        tree.commit("foo")
        self.assertEqual(
            ['Committing with 1 patches applied and 1 patches unapplied.'],
            warnings)
        self.assertPathExists("source/.pc/applied-patches")
        self.assertPathExists("source/a")
        self.assertPathDoesNotExist("source/b")
