# Copyright (C) 2020 Jelmer Vernooij <jelmer@jelmer.uk>
#
# 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 for breezy.git.tests."""

import stat

from dulwich.objects import Blob, Tree

from breezy.bzr.inventorytree import InventoryTreeChange as TreeChange
from breezy.delta import TreeDelta
from breezy.errors import PathsNotVersionedError
from breezy.git.mapping import default_mapping
from breezy.git.tree import changes_from_git_changes, tree_delta_from_git_changes
from breezy.tests import TestCase, TestCaseWithTransport
from breezy.workingtree import WorkingTree

REG_MODE = stat.S_IFREG | 0o644


class ChangesFromGitChangesTests(TestCase):
    def setUp(self):
        super().setUp()
        self.maxDiff = None
        self.mapping = default_mapping

    def transform(
        self,
        changes,
        specific_files=None,
        include_unchanged=False,
        source_extras=None,
        target_extras=None,
    ):
        return list(
            changes_from_git_changes(
                changes,
                self.mapping,
                specific_files=specific_files,
                include_unchanged=include_unchanged,
                source_extras=source_extras,
                target_extras=target_extras,
            )
        )

    def test_empty(self):
        self.assertEqual([], self.transform([]))

    def test_modified(self):
        a = Blob.from_string(b"a")
        b = Blob.from_string(b"b")
        self.assertEqual(
            [
                TreeChange(
                    b"git:a",
                    ("a", "a"),
                    True,
                    (True, True),
                    (b"TREE_ROOT", b"TREE_ROOT"),
                    ("a", "a"),
                    ("file", "file"),
                    (False, False),
                    False,
                )
            ],
            self.transform(
                [
                    (
                        "modify",
                        (b"a", stat.S_IFREG | 0o644, a),
                        (b"a", stat.S_IFREG | 0o644, b),
                    )
                ]
            ),
        )

    def test_kind_changed(self):
        a = Blob.from_string(b"a")
        b = Blob.from_string(b"target")
        self.assertEqual(
            [
                TreeChange(
                    b"git:a",
                    ("a", "a"),
                    True,
                    (True, True),
                    (b"TREE_ROOT", b"TREE_ROOT"),
                    ("a", "a"),
                    ("file", "symlink"),
                    (False, False),
                    False,
                )
            ],
            self.transform(
                [("modify", (b"a", stat.S_IFREG | 0o644, a), (b"a", stat.S_IFLNK, b))]
            ),
        )

    def test_rename_no_changes(self):
        a = Blob.from_string(b"a")
        self.assertEqual(
            [
                TreeChange(
                    b"git:old",
                    ("old", "a"),
                    False,
                    (True, True),
                    (b"TREE_ROOT", b"TREE_ROOT"),
                    ("old", "a"),
                    ("file", "file"),
                    (False, False),
                    False,
                )
            ],
            self.transform(
                [
                    (
                        "rename",
                        (b"old", stat.S_IFREG | 0o644, a),
                        (b"a", stat.S_IFREG | 0o644, a),
                    )
                ]
            ),
        )

    def test_rename_and_modify(self):
        a = Blob.from_string(b"a")
        b = Blob.from_string(b"b")
        self.assertEqual(
            [
                TreeChange(
                    b"git:a",
                    ("a", "b"),
                    True,
                    (True, True),
                    (b"TREE_ROOT", b"TREE_ROOT"),
                    ("a", "b"),
                    ("file", "file"),
                    (False, False),
                    False,
                )
            ],
            self.transform(
                [
                    (
                        "rename",
                        (b"a", stat.S_IFREG | 0o644, a),
                        (b"b", stat.S_IFREG | 0o644, b),
                    )
                ]
            ),
        )

    def test_copy_no_changes(self):
        a = Blob.from_string(b"a")
        self.assertEqual(
            [
                TreeChange(
                    b"git:a",
                    ("old", "a"),
                    False,
                    (True, True),
                    (b"TREE_ROOT", b"TREE_ROOT"),
                    ("old", "a"),
                    ("file", "file"),
                    (False, False),
                    True,
                )
            ],
            self.transform(
                [
                    (
                        "copy",
                        (b"old", stat.S_IFREG | 0o644, a),
                        (b"a", stat.S_IFREG | 0o644, a),
                    )
                ]
            ),
        )

    def test_copy_and_modify(self):
        a = Blob.from_string(b"a")
        b = Blob.from_string(b"b")
        self.assertEqual(
            [
                TreeChange(
                    b"git:b",
                    ("a", "b"),
                    True,
                    (True, True),
                    (b"TREE_ROOT", b"TREE_ROOT"),
                    ("a", "b"),
                    ("file", "file"),
                    (False, False),
                    True,
                )
            ],
            self.transform(
                [
                    (
                        "copy",
                        (b"a", stat.S_IFREG | 0o644, a),
                        (b"b", stat.S_IFREG | 0o644, b),
                    )
                ]
            ),
        )

    def test_add(self):
        b = Blob.from_string(b"b")
        self.assertEqual(
            [
                TreeChange(
                    b"git:a",
                    (None, "a"),
                    True,
                    (False, True),
                    (None, b"TREE_ROOT"),
                    (None, "a"),
                    (None, "file"),
                    (None, False),
                    False,
                )
            ],
            self.transform(
                [("add", (None, None, None), (b"a", stat.S_IFREG | 0o644, b))]
            ),
        )

    def test_delete(self):
        b = Blob.from_string(b"b")
        self.assertEqual(
            [
                TreeChange(
                    b"git:a",
                    ("a", None),
                    True,
                    (True, False),
                    (b"TREE_ROOT", None),
                    ("a", None),
                    ("file", None),
                    (False, None),
                    False,
                )
            ],
            self.transform(
                [("remove", (b"a", stat.S_IFREG | 0o644, b), (None, None, None))]
            ),
        )

    def test_unchanged(self):
        b = Blob.from_string(b"b")
        self.assertEqual(
            [
                TreeChange(
                    b"git:a",
                    ("a", "a"),
                    False,
                    (True, True),
                    (b"TREE_ROOT", b"TREE_ROOT"),
                    ("a", "a"),
                    ("file", "file"),
                    (False, False),
                    False,
                )
            ],
            self.transform(
                [
                    (
                        "unchanged",
                        (b"a", stat.S_IFREG | 0o644, b),
                        (b"a", stat.S_IFREG | 0o644, b),
                    )
                ],
                include_unchanged=True,
            ),
        )
        self.assertEqual(
            [],
            self.transform(
                [
                    (
                        "unchanged",
                        (b"a", stat.S_IFREG | 0o644, b),
                        (b"a", stat.S_IFREG | 0o644, b),
                    )
                ],
                include_unchanged=False,
            ),
        )

    def test_unversioned(self):
        b = Blob.from_string(b"b")
        self.assertEqual(
            [
                TreeChange(
                    None,
                    (None, "a"),
                    True,
                    (False, False),
                    (None, b"TREE_ROOT"),
                    (None, "a"),
                    (None, "file"),
                    (None, False),
                    False,
                )
            ],
            self.transform(
                [("add", (None, None, None), (b"a", stat.S_IFREG | 0o644, b))],
                target_extras={b"a"},
            ),
        )
        self.assertEqual(
            [
                TreeChange(
                    None,
                    ("a", "a"),
                    False,
                    (False, False),
                    (b"TREE_ROOT", b"TREE_ROOT"),
                    ("a", "a"),
                    ("file", "file"),
                    (False, False),
                    False,
                )
            ],
            self.transform(
                [
                    (
                        "add",
                        (b"a", stat.S_IFREG | 0o644, b),
                        (b"a", stat.S_IFREG | 0o644, b),
                    )
                ],
                source_extras={b"a"},
                target_extras={b"a"},
            ),
        )


class DeltaFromGitChangesTests(TestCase):
    def setUp(self):
        super().setUp()
        self.maxDiff = None
        self.mapping = default_mapping

    def transform(
        self,
        changes,
        specific_files=None,
        require_versioned=False,
        include_root=False,
        source_extras=None,
        target_extras=None,
    ):
        return tree_delta_from_git_changes(
            changes,
            (self.mapping, self.mapping),
            specific_files=specific_files,
            require_versioned=require_versioned,
            include_root=include_root,
            source_extras=source_extras,
            target_extras=target_extras,
        )

    def test_empty(self):
        self.assertEqual(TreeDelta(), self.transform([]))

    def test_modified(self):
        a = Blob.from_string(b"a")
        b = Blob.from_string(b"b")
        delta = self.transform(
            [
                (
                    "modify",
                    (b"a", stat.S_IFREG | 0o644, a),
                    (b"a", stat.S_IFREG | 0o644, b),
                )
            ]
        )
        expected_delta = TreeDelta()
        expected_delta.modified.append(
            TreeChange(
                b"git:a",
                ("a", "a"),
                True,
                (True, True),
                (b"TREE_ROOT", b"TREE_ROOT"),
                ("a", "a"),
                ("file", "file"),
                (False, False),
                False,
            )
        )
        self.assertEqual(expected_delta, delta)

    def test_rename_no_changes(self):
        a = Blob.from_string(b"a")
        delta = self.transform(
            [
                (
                    "rename",
                    (b"old", stat.S_IFREG | 0o644, a),
                    (b"a", stat.S_IFREG | 0o644, a),
                )
            ]
        )
        expected_delta = TreeDelta()
        expected_delta.renamed.append(
            TreeChange(
                b"git:old",
                ("old", "a"),
                False,
                (True, True),
                (b"TREE_ROOT", b"TREE_ROOT"),
                ("old", "a"),
                ("file", "file"),
                (False, False),
                False,
            )
        )
        self.assertEqual(expected_delta, delta)

    def test_rename_and_modify(self):
        a = Blob.from_string(b"a")
        b = Blob.from_string(b"b")
        delta = self.transform(
            [
                (
                    "rename",
                    (b"a", stat.S_IFREG | 0o644, a),
                    (b"b", stat.S_IFREG | 0o644, b),
                )
            ]
        )
        expected_delta = TreeDelta()
        expected_delta.renamed.append(
            TreeChange(
                b"git:a",
                ("a", "b"),
                True,
                (True, True),
                (b"TREE_ROOT", b"TREE_ROOT"),
                ("a", "b"),
                ("file", "file"),
                (False, False),
                False,
            )
        )
        self.assertEqual(delta, expected_delta)

    def test_copy_no_changes(self):
        a = Blob.from_string(b"a")
        delta = self.transform(
            [
                (
                    "copy",
                    (b"old", stat.S_IFREG | 0o644, a),
                    (b"a", stat.S_IFREG | 0o644, a),
                )
            ]
        )
        expected_delta = TreeDelta()
        expected_delta.copied.append(
            TreeChange(
                b"git:a",
                ("old", "a"),
                False,
                (True, True),
                (b"TREE_ROOT", b"TREE_ROOT"),
                ("old", "a"),
                ("file", "file"),
                (False, False),
                True,
            )
        )
        self.assertEqual(expected_delta, delta)

    def test_copy_and_modify(self):
        a = Blob.from_string(b"a")
        b = Blob.from_string(b"b")
        delta = self.transform(
            [("copy", (b"a", stat.S_IFREG | 0o644, a), (b"b", stat.S_IFREG | 0o644, b))]
        )
        expected_delta = TreeDelta()
        expected_delta.copied.append(
            TreeChange(
                b"git:b",
                ("a", "b"),
                True,
                (True, True),
                (b"TREE_ROOT", b"TREE_ROOT"),
                ("a", "b"),
                ("file", "file"),
                (False, False),
                True,
            )
        )
        self.assertEqual(expected_delta, delta)

    def test_add(self):
        b = Blob.from_string(b"b")
        delta = self.transform(
            [("add", (None, None, None), (b"a", stat.S_IFREG | 0o644, b))]
        )
        expected_delta = TreeDelta()
        expected_delta.added.append(
            TreeChange(
                b"git:a",
                (None, "a"),
                True,
                (False, True),
                (None, b"TREE_ROOT"),
                (None, "a"),
                (None, "file"),
                (None, False),
                False,
            )
        )
        self.assertEqual(delta, expected_delta)

    def test_delete(self):
        b = Blob.from_string(b"b")
        delta = self.transform(
            [("remove", (b"a", stat.S_IFREG | 0o644, b), (None, None, None))]
        )
        expected_delta = TreeDelta()
        expected_delta.removed.append(
            TreeChange(
                b"git:a",
                ("a", None),
                True,
                (True, False),
                (b"TREE_ROOT", None),
                ("a", None),
                ("file", None),
                (False, None),
                False,
            )
        )
        self.assertEqual(delta, expected_delta)

    def test_unchanged(self):
        b = Blob.from_string(b"b")
        delta = self.transform(
            [
                (
                    "unchanged",
                    (b"a", stat.S_IFREG | 0o644, b),
                    (b"a", stat.S_IFREG | 0o644, b),
                )
            ]
        )
        expected_delta = TreeDelta()
        expected_delta.unchanged.append(
            TreeChange(
                b"git:a",
                ("a", "a"),
                False,
                (True, True),
                (b"TREE_ROOT", b"TREE_ROOT"),
                ("a", "a"),
                ("file", "file"),
                (False, False),
                False,
            )
        )

    def test_unversioned(self):
        b = Blob.from_string(b"b")
        delta = self.transform(
            [("add", (None, None, None), (b"a", stat.S_IFREG | 0o644, b))],
            target_extras={b"a"},
        )
        expected_delta = TreeDelta()
        expected_delta.unversioned.append(
            TreeChange(
                None,
                (None, "a"),
                True,
                (False, False),
                (None, b"TREE_ROOT"),
                (None, "a"),
                (None, "file"),
                (None, False),
                False,
            )
        )
        self.assertEqual(delta, expected_delta)
        delta = self.transform(
            [("add", (b"a", stat.S_IFREG | 0o644, b), (b"a", stat.S_IFREG | 0o644, b))],
            source_extras={b"a"},
            target_extras={b"a"},
        )
        expected_delta = TreeDelta()
        expected_delta.unversioned.append(
            TreeChange(
                None,
                ("a", "a"),
                False,
                (False, False),
                (b"TREE_ROOT", b"TREE_ROOT"),
                ("a", "a"),
                ("file", "file"),
                (False, False),
                False,
            )
        )
        self.assertEqual(delta, expected_delta)

    def test_kind_change(self):
        a = Blob.from_string(b"a")
        b = Blob.from_string(b"target")
        delta = self.transform(
            [("modify", (b"a", stat.S_IFREG | 0o644, a), (b"a", stat.S_IFLNK, b))]
        )
        expected_delta = TreeDelta()
        expected_delta.kind_changed.append(
            TreeChange(
                b"git:a",
                ("a", "a"),
                True,
                (True, True),
                (b"TREE_ROOT", b"TREE_ROOT"),
                ("a", "a"),
                ("file", "symlink"),
                (False, False),
                False,
            )
        )
        self.assertEqual(expected_delta, delta)


class FindRelatedPathsAcrossTrees(TestCaseWithTransport):
    def test_none(self):
        self.make_branch_and_tree("t1", format="git")
        wt = WorkingTree.open("t1")
        self.assertIs(None, wt.find_related_paths_across_trees(None))

    def test_empty(self):
        self.make_branch_and_tree("t1", format="git")
        wt = WorkingTree.open("t1")
        self.assertEqual([], list(wt.find_related_paths_across_trees([])))

    def test_directory(self):
        self.make_branch_and_tree("t1", format="git")
        wt = WorkingTree.open("t1")
        self.build_tree(["t1/dir/", "t1/dir/file"])
        wt.add(["dir", "dir/file"])
        self.assertEqual(
            ["dir/file"], list(wt.find_related_paths_across_trees(["dir/file"]))
        )
        self.assertEqual(["dir"], list(wt.find_related_paths_across_trees(["dir"])))

    def test_empty_directory(self):
        self.make_branch_and_tree("t1", format="git")
        wt = WorkingTree.open("t1")
        self.build_tree(["t1/dir/"])
        wt.add(["dir"])
        self.assertEqual(["dir"], list(wt.find_related_paths_across_trees(["dir"])))
        self.assertRaises(
            PathsNotVersionedError, wt.find_related_paths_across_trees, ["dir/file"]
        )

    def test_missing(self):
        self.make_branch_and_tree("t1", format="git")
        wt = WorkingTree.open("t1")
        self.assertRaises(
            PathsNotVersionedError, wt.find_related_paths_across_trees, ["file"]
        )

    def test_not_versioned(self):
        self.make_branch_and_tree("t1", format="git")
        self.make_branch_and_tree("t2", format="git")
        wt1 = WorkingTree.open("t1")
        wt2 = WorkingTree.open("t2")
        self.build_tree(["t1/file"])
        self.build_tree(["t2/file"])
        self.assertRaises(
            PathsNotVersionedError, wt1.find_related_paths_across_trees, ["file"], [wt2]
        )

    def test_single(self):
        self.make_branch_and_tree("t1", format="git")
        wt = WorkingTree.open("t1")
        self.build_tree(["t1/file"])
        wt.add("file")
        self.assertEqual(["file"], list(wt.find_related_paths_across_trees(["file"])))
