diff --git a/dvc/command/diff.py b/dvc/command/diff.py index a0f8b3419e..380a67e551 100644 --- a/dvc/command/diff.py +++ b/dvc/command/diff.py @@ -11,19 +11,31 @@ logger = logging.getLogger(__name__) -def _show_md(diff): +def _digest(checksum): + if isinstance(checksum, str): + return checksum[0:8] + return "{}..{}".format(checksum["old"][0:8], checksum["new"][0:8]) + + +def _show_md(diff, show_hash=False): from dvc.utils.diff import table + header = ["Status", "Hash", "Path"] if show_hash else ["Status", "Path"] rows = [] for status in ["added", "deleted", "modified"]: entries = diff.get(status, []) if not entries: continue - paths = sorted(entry["path"] for entry in entries) - for path in paths: - rows.append([status, path]) + sorted_entries = sorted(entries, key=lambda entry: entry["path"]) + for entry in sorted_entries: + path = entry["path"] + if show_hash: + check_sum = _digest(entry.get("hash", "")) + rows.append([status, check_sum, path]) + else: + rows.append([status, path]) - return table(["Status", "Path"], rows, True) + return table(header, rows, True) class CmdDiff(CmdBase): @@ -55,11 +67,6 @@ def _format(diff): At the bottom, include a summary with the number of files per state. """ - def _digest(checksum): - if isinstance(checksum, str): - return checksum[0:8] - return "{}..{}".format(checksum["old"][0:8], checksum["new"][0:8]) - colors = { "added": colorama.Fore.GREEN, "modified": colorama.Fore.YELLOW, @@ -111,7 +118,8 @@ def run(self): try: diff = self.repo.diff(self.args.a_rev, self.args.b_rev) - if not self.args.show_hash: + show_hash = self.args.show_hash + if not show_hash: for _, entries in diff.items(): for entry in entries: del entry["hash"] @@ -119,7 +127,7 @@ def run(self): if self.args.show_json: logger.info(json.dumps(diff)) elif self.args.show_md: - logger.info(_show_md(diff)) + logger.info(_show_md(diff, show_hash)) elif diff: logger.info(self._format(diff)) diff --git a/tests/unit/command/test_diff.py b/tests/unit/command/test_diff.py index 529cbe37ad..e1f050a452 100644 --- a/tests/unit/command/test_diff.py +++ b/tests/unit/command/test_diff.py @@ -2,8 +2,23 @@ import logging import os +import mock +import pytest + from dvc.cli import parse_args -from dvc.command.diff import _show_md +from dvc.command.diff import _digest, _show_md + + +@pytest.mark.parametrize( + "checksum, expected", + [ + ("wxyz1234pq", "wxyz1234"), + (dict(old="1234567890", new="0987654321"), "12345678..09876543"), + ], + ids=["str", "dict"], +) +def test_digest(checksum, expected): + assert expected == _digest(checksum) def test_default(mocker, caplog): @@ -90,6 +105,21 @@ def test_show_json_and_hash(mocker, caplog): assert '"modified": []' in caplog.text +@pytest.mark.parametrize("show_hash", [None, True, False]) +@mock.patch("dvc.command.diff._show_md") +def test_diff_show_md_and_hash(mock_show_md, mocker, caplog, show_hash): + options = ["diff", "--show-md"] + (["--show-hash"] if show_hash else []) + args = parse_args(options) + cmd = args.func(args) + + diff = {} + show_hash = show_hash if show_hash else False + mocker.patch("dvc.repo.Repo.diff", return_value=diff) + + assert 0 == cmd.run() + mock_show_md.assert_called_once_with(diff, show_hash) + + def test_no_changes(mocker, caplog): args = parse_args(["diff", "--show-json"]) cmd = args.func(args) @@ -120,15 +150,13 @@ def test_show_md_empty(): def test_show_md(): diff = { "deleted": [ - {"path": "zoo", "hash": "22222"}, - {"path": os.path.join("data", ""), "hash": "XXXXXXXX.dir"}, - {"path": os.path.join("data", "foo"), "hash": "11111111"}, - {"path": os.path.join("data", "bar"), "hash": "00000000"}, - ], - "modified": [ - {"path": "file", "hash": {"old": "AAAAAAAA", "new": "BBBBBBBB"}} + {"path": "zoo"}, + {"path": os.path.join("data", "")}, + {"path": os.path.join("data", "foo")}, + {"path": os.path.join("data", "bar")}, ], - "added": [{"path": "file", "hash": "00000000"}], + "modified": [{"path": "file"}], + "added": [{"path": "file"}], } assert _show_md(diff) == ( "| Status | Path |\n" @@ -140,3 +168,28 @@ def test_show_md(): "| deleted | zoo |\n" "| modified | file |\n" ).format(sep=os.path.sep) + + +def test_show_md_with_hash(): + diff = { + "deleted": [ + {"path": "zoo", "hash": "22222"}, + {"path": os.path.join("data", ""), "hash": "XXXXXXXX.dir"}, + {"path": os.path.join("data", "foo"), "hash": "11111111"}, + {"path": os.path.join("data", "bar"), "hash": "00000000"}, + ], + "modified": [ + {"path": "file", "hash": {"old": "AAAAAAAA", "new": "BBBBBBBB"}} + ], + "added": [{"path": "file", "hash": "00000000"}], + } + assert _show_md(diff, show_hash=True) == ( + "| Status | Hash | Path |\n" + "|----------|--------------------|----------|\n" + "| added | 00000000 | file |\n" + "| deleted | XXXXXXXX | data{sep} |\n" + "| deleted | 00000000 | data{sep}bar |\n" + "| deleted | 11111111 | data{sep}foo |\n" + "| deleted | 22222 | zoo |\n" + "| modified | AAAAAAAA..BBBBBBBB | file |\n" + ).format(sep=os.path.sep)