<commit_before>import hashlib
import json
import os
import pytest

import colorama

from dvc.main import main
from dvc.exceptions import DvcException


def digest(text):
    return hashlib.md5(bytes(text, "utf-8")).hexdigest()


def test_no_scm(tmp_dir, dvc):
    tmp_dir.dvc_gen("file", "text")

    with pytest.raises(DvcException, match=r"only supported for Git repos"):
        dvc.diff()


def test_added(tmp_dir, scm, dvc):
    tmp_dir.dvc_gen("file", "text")

    result = {
        "added": [{"filename": "file", "checksum": digest("text")}],
        "deleted": [],
        "modified": [],
    }

    assert result == dvc.diff()


def test_deleted(tmp_dir, scm, dvc):
    tmp_dir.dvc_gen("file", "text", commit="add file")
    (tmp_dir / "file.dvc").unlink()

    result = {
        "added": [],
        "deleted": [{"filename": "file", "checksum": digest("text")}],
        "modified": [],
    }

    assert result == dvc.diff()


def test_modified(tmp_dir, scm, dvc):
    tmp_dir.dvc_gen("file", "first", commit="first version")
    tmp_dir.dvc_gen("file", "second")

    result = {
        "added": [],
        "deleted": [],
        "modified": [
            {
                "filename": "file",
                "checksum": {"old": digest("first"), "new": digest("second")},
            }
        ],
    }

    assert result == dvc.diff()


def test_refs(tmp_dir, scm, dvc):
    tmp_dir.dvc_gen("file", "first", commit="first version")
    tmp_dir.dvc_gen("file", "second", commit="second version")
    tmp_dir.dvc_gen("file", "third", commit="third version")

    HEAD_2 = digest("first")
    HEAD_1 = digest("second")
    HEAD = digest("third")

    assert dvc.diff("HEAD~1") == {
        "added": [],
        "deleted": [],
        "modified": [
            {"filename": "file", "checksum": {"old": HEAD_1, "new": HEAD}}
        ],
    }

    assert dvc.diff("HEAD~2", "HEAD~1") == {
        "added": [],
        "deleted": [],
        "modified": [
            {"filename": "file", "checksum": {"old": HEAD_2, "new": HEAD_1}}
        ],
    }

    with pytest.raises(
        DvcException, match=r"failed to access revision 'missing'"
    ):
        dvc.diff("missing")


def test_target(tmp_dir, scm, dvc):
    tmp_dir.dvc_gen("foo", "foo")
    tmp_dir.dvc_gen("bar", "bar")
    scm.add([".gitignore", "foo.dvc", "bar.dvc"])
    scm.commit("lowercase")

    tmp_dir.dvc_gen("foo", "FOO")
    tmp_dir.dvc_gen("bar", "BAR")
    scm.add(["foo.dvc", "bar.dvc"])
    scm.commit("uppercase")

    assert dvc.diff("HEAD~1", target="foo") == {
        "added": [],
        "deleted": [],
        "modified": [
            {
                "filename": "foo",
                "checksum": {"old": digest("foo"), "new": digest("FOO")},
            }
        ],
    }

    assert dvc.diff("HEAD~1", target="missing") == {
        "added": [],
        "deleted": [],
        "modified": [],
    }


def test_directories(tmp_dir, scm, dvc):
    tmp_dir.dvc_gen({"dir": {"1": "1", "2": "2"}}, commit="add a directory")
    tmp_dir.dvc_gen({"dir": {"3": "3"}}, commit="add a file")
    tmp_dir.dvc_gen({"dir": {"2": "two"}}, commit="modify a file")

    (tmp_dir / "dir" / "2").unlink()
    dvc.add("dir")
    scm.add("dir.dvc")
    scm.commit("delete a file")

    assert dvc.diff(":/init", ":/directory") == {
        "added": [
            {
                "filename": os.path.join("dir", ""),
                "checksum": "5fb6b29836c388e093ca0715c872fe2a.dir",
            },
            {"filename": os.path.join("dir", "1"), "checksum": digest("1")},
            {"filename": os.path.join("dir", "2"), "checksum": digest("2")},
        ],
        "deleted": [],
        "modified": [],
    }

    assert dvc.diff(":/directory", ":/modify") == {
        "added": [
            {"filename": os.path.join("dir", "3"), "checksum": digest("3")}
        ],
        "deleted": [],
        "modified": [
            {
                "filename": os.path.join("dir", ""),
                "checksum": {
                    "old": "5fb6b29836c388e093ca0715c872fe2a.dir",
                    "new": "9b5faf37366b3370fd98e3e60ca439c1.dir",
                },
            },
            {
                "filename": os.path.join("dir", "2"),
                "checksum": {"old": digest("2"), "new": digest("two")},
            },
        ],
    }

    assert dvc.diff(":/modify", ":/delete") == {
        "added": [],
        "deleted": [
            {"filename": os.path.join("dir", "2"), "checksum": digest("two")}
        ],
        "modified": [
            {
                "filename": os.path.join("dir", ""),
                "checksum": {
                    "old": "9b5faf37366b3370fd98e3e60ca439c1.dir",
                    "new": "83ae82fb367ac9926455870773ff09e6.dir",
                },
            }
        ],
    }


def test_json(tmp_dir, scm, dvc, mocker, capsys):
    assert 0 == main(["diff", "--json"])
    assert not capsys.readouterr().out

    diff = {
        "added": [{"filename": "file", "checksum": digest("text")}],
        "deleted": [],
        "modified": [],
    }
    mocker.patch("dvc.repo.Repo.diff", return_value=diff)
    assert 0 == main(["diff", "--json"])
    assert (
        json.dumps(
            {
                "added": [{"filename": "file", "checksum": digest("text")}],
                "deleted": [],
                "modified": [],
            }
        )
        in capsys.readouterr().out
    )


def test_cli(tmp_dir, scm, dvc, mocker, capsys):
    assert 0 == main(["diff"])
    assert not capsys.readouterr().out

    diff = {
        "added": [{"filename": "file", "checksum": digest("text")}],
        "deleted": [],
        "modified": [],
    }
    mocker.patch("dvc.repo.Repo.diff", return_value=diff)
    assert 0 == main(["diff"])
    assert capsys.readouterr().out == (
        "{green}Added{nc}:\n"
        "    file\n".format(green=colorama.Fore.GREEN, nc=colorama.Fore.RESET)
    )

    diff = {
        "added": [],
        "deleted": [
            {"filename": "foo", "checksum": digest("foo")},
            {"filename": "bar", "checksum": digest("bar")},
        ],
        "modified": [
            {
                "filename": "file",
                "checksum": {"old": digest("first"), "new": digest("second")},
            }
        ],
    }
    mocker.patch("dvc.repo.Repo.diff", return_value=diff)
    assert 0 == main(["diff", "--checksums"])
    assert capsys.readouterr().out == (
        "{red}Deleted{nc}:\n"
        "    37b51d19  bar\n"
        "    acbd18db  foo\n"
        "\n"
        "{yellow}Modified{nc}:\n"
        "    8b04d5e3..a9f0e61a  file\n".format(
            red=colorama.Fore.RED,
            yellow=colorama.Fore.YELLOW,
            nc=colorama.Fore.RESET,
        )
    )
<commit_msg>diff: enable using diff when there is no cache<commit_after>import hashlib
import json
import os
import pytest
import shutil

import colorama

from dvc.compat import fspath
from dvc.exceptions import DvcException
from dvc.main import main


def digest(text):
    return hashlib.md5(bytes(text, "utf-8")).hexdigest()


def test_no_scm(tmp_dir, dvc):
    tmp_dir.dvc_gen("file", "text")

    with pytest.raises(DvcException, match=r"only supported for Git repos"):
        dvc.diff()


def test_added(tmp_dir, scm, dvc):
    tmp_dir.dvc_gen("file", "text")

    assert dvc.diff() == {
        "added": [{"filename": "file", "checksum": digest("text")}],
        "deleted": [],
        "modified": [],
    }


def test_no_cache_entry(tmp_dir, scm, dvc):
    tmp_dir.dvc_gen("file", "text")
    shutil.rmtree(fspath(tmp_dir / ".dvc" / "cache"))

    assert dvc.diff() == {
        "added": [{"filename": "file", "checksum": digest("text")}],
        "deleted": [],
        "modified": [],
    }


def test_deleted(tmp_dir, scm, dvc):
    tmp_dir.dvc_gen("file", "text", commit="add file")
    (tmp_dir / "file.dvc").unlink()

    assert dvc.diff() == {
        "added": [],
        "deleted": [{"filename": "file", "checksum": digest("text")}],
        "modified": [],
    }


def test_modified(tmp_dir, scm, dvc):
    tmp_dir.dvc_gen("file", "first", commit="first version")
    tmp_dir.dvc_gen("file", "second")

    assert dvc.diff() == {
        "added": [],
        "deleted": [],
        "modified": [
            {
                "filename": "file",
                "checksum": {"old": digest("first"), "new": digest("second")},
            }
        ],
    }


def test_refs(tmp_dir, scm, dvc):
    tmp_dir.dvc_gen("file", "first", commit="first version")
    tmp_dir.dvc_gen("file", "second", commit="second version")
    tmp_dir.dvc_gen("file", "third", commit="third version")

    HEAD_2 = digest("first")
    HEAD_1 = digest("second")
    HEAD = digest("third")

    assert dvc.diff("HEAD~1") == {
        "added": [],
        "deleted": [],
        "modified": [
            {"filename": "file", "checksum": {"old": HEAD_1, "new": HEAD}}
        ],
    }

    assert dvc.diff("HEAD~2", "HEAD~1") == {
        "added": [],
        "deleted": [],
        "modified": [
            {"filename": "file", "checksum": {"old": HEAD_2, "new": HEAD_1}}
        ],
    }

    with pytest.raises(
        DvcException, match=r"failed to access revision 'missing'"
    ):
        dvc.diff("missing")


def test_target(tmp_dir, scm, dvc):
    tmp_dir.dvc_gen("foo", "foo")
    tmp_dir.dvc_gen("bar", "bar")
    scm.add([".gitignore", "foo.dvc", "bar.dvc"])
    scm.commit("lowercase")

    tmp_dir.dvc_gen("foo", "FOO")
    tmp_dir.dvc_gen("bar", "BAR")
    scm.add(["foo.dvc", "bar.dvc"])
    scm.commit("uppercase")

    assert dvc.diff("HEAD~1", target="foo") == {
        "added": [],
        "deleted": [],
        "modified": [
            {
                "filename": "foo",
                "checksum": {"old": digest("foo"), "new": digest("FOO")},
            }
        ],
    }

    assert dvc.diff("HEAD~1", target="missing") == {
        "added": [],
        "deleted": [],
        "modified": [],
    }


def test_directories(tmp_dir, scm, dvc):
    tmp_dir.dvc_gen({"dir": {"1": "1", "2": "2"}}, commit="add a directory")
    tmp_dir.dvc_gen({"dir": {"3": "3"}}, commit="add a file")
    tmp_dir.dvc_gen({"dir": {"2": "two"}}, commit="modify a file")

    (tmp_dir / "dir" / "2").unlink()
    dvc.add("dir")
    scm.add("dir.dvc")
    scm.commit("delete a file")

    assert dvc.diff(":/init", ":/directory") == {
        "added": [
            {
                "filename": os.path.join("dir", ""),
                "checksum": "5fb6b29836c388e093ca0715c872fe2a.dir",
            },
            {"filename": os.path.join("dir", "1"), "checksum": digest("1")},
            {"filename": os.path.join("dir", "2"), "checksum": digest("2")},
        ],
        "deleted": [],
        "modified": [],
    }

    assert dvc.diff(":/directory", ":/modify") == {
        "added": [
            {"filename": os.path.join("dir", "3"), "checksum": digest("3")}
        ],
        "deleted": [],
        "modified": [
            {
                "filename": os.path.join("dir", ""),
                "checksum": {
                    "old": "5fb6b29836c388e093ca0715c872fe2a.dir",
                    "new": "9b5faf37366b3370fd98e3e60ca439c1.dir",
                },
            },
            {
                "filename": os.path.join("dir", "2"),
                "checksum": {"old": digest("2"), "new": digest("two")},
            },
        ],
    }

    assert dvc.diff(":/modify", ":/delete") == {
        "added": [],
        "deleted": [
            {"filename": os.path.join("dir", "2"), "checksum": digest("two")}
        ],
        "modified": [
            {
                "filename": os.path.join("dir", ""),
                "checksum": {
                    "old": "9b5faf37366b3370fd98e3e60ca439c1.dir",
                    "new": "83ae82fb367ac9926455870773ff09e6.dir",
                },
            }
        ],
    }


def test_json(tmp_dir, scm, dvc, mocker, capsys):
    assert 0 == main(["diff", "--json"])
    assert not capsys.readouterr().out

    diff = {
        "added": [{"filename": "file", "checksum": digest("text")}],
        "deleted": [],
        "modified": [],
    }
    mocker.patch("dvc.repo.Repo.diff", return_value=diff)
    assert 0 == main(["diff", "--json"])
    assert (
        json.dumps(
            {
                "added": [{"filename": "file", "checksum": digest("text")}],
                "deleted": [],
                "modified": [],
            }
        )
        in capsys.readouterr().out
    )


def test_cli(tmp_dir, scm, dvc, mocker, capsys):
    assert 0 == main(["diff"])
    assert not capsys.readouterr().out

    diff = {
        "added": [{"filename": "file", "checksum": digest("text")}],
        "deleted": [],
        "modified": [],
    }
    mocker.patch("dvc.repo.Repo.diff", return_value=diff)
    assert 0 == main(["diff"])
    assert capsys.readouterr().out == (
        "{green}Added{nc}:\n"
        "    file\n".format(green=colorama.Fore.GREEN, nc=colorama.Fore.RESET)
    )

    diff = {
        "added": [],
        "deleted": [
            {"filename": "foo", "checksum": digest("foo")},
            {"filename": "bar", "checksum": digest("bar")},
        ],
        "modified": [
            {
                "filename": "file",
                "checksum": {"old": digest("first"), "new": digest("second")},
            }
        ],
    }
    mocker.patch("dvc.repo.Repo.diff", return_value=diff)
    assert 0 == main(["diff", "--checksums"])
    assert capsys.readouterr().out == (
        "{red}Deleted{nc}:\n"
        "    37b51d19  bar\n"
        "    acbd18db  foo\n"
        "\n"
        "{yellow}Modified{nc}:\n"
        "    8b04d5e3..a9f0e61a  file\n".format(
            red=colorama.Fore.RED,
            yellow=colorama.Fore.YELLOW,
            nc=colorama.Fore.RESET,
        )
    )

{'fraction_non_alphanumeric': 0.20475847152126891, 'fraction_numerical': 0.022494592645998557, 'mean_word_length': 2.03125, 'pattern_counts': {'":': 227, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5075193', 'n_tokens_mistral': 4964, 'n_tokens_neox': 4749, 'n_words': 816}