diff --git a/dvc/repo/diff.py b/dvc/repo/diff.py index 381f779f6..3ef1d890a 100644 --- a/dvc/repo/diff.py +++ b/dvc/repo/diff.py @@ -40,8 +40,9 @@ def diff(self, a_ref="HEAD", b_ref=None, *, target=None): By default, it compares the working tree with the last commit's tree. When a `target` path is given, it only shows that file's comparison. + It will silently fail when `target` is not found in any of the trees. - This implementation differs from `git diff`, since DVC doesn't have + This implementation differs from `git diff` since DVC doesn't have the concept of `index`, `dvc diff` would be the same as `dvc diff HEAD`. """ outs = {} @@ -52,11 +53,13 @@ def diff(self, a_ref="HEAD", b_ref=None, *, target=None): for stage in self.stages for out in stage.outs ) - old = outs[a_ref] new = outs[b_ref or "working tree"] delta = old ^ new + if target: + delta = set(x for x in delta if x.filename == target) + if not delta: return diff --git a/tests/func/test_diff.py b/tests/func/test_diff.py index 9a703a5df..12e56db19 100644 --- a/tests/func/test_diff.py +++ b/tests/func/test_diff.py @@ -1,4 +1,9 @@ import pytest +import hashlib + + +def _checksum(text): + return hashlib.md5(bytes(text, "utf-8")).hexdigest() def test_no_scm(tmp_dir, dvc): @@ -13,7 +18,7 @@ def test_added(tmp_dir, scm, dvc): result = { "file": { "old": {}, - "new": {"checksum": "1cb251ec0d568de6a929b520c4aed8d1", "size": 4}, + "new": {"checksum": _checksum("text"), "size": 4}, "diff": {"status": "added", "size": 4}, } } @@ -27,7 +32,7 @@ def test_deleted(tmp_dir, scm, dvc): result = { "file": { - "old": {"checksum": "1cb251ec0d568de6a929b520c4aed8d1", "size": 4}, + "old": {"checksum": _checksum("text"), "size": 4}, "new": {}, "diff": {"status": "deleted", "size": 4}, } @@ -42,8 +47,8 @@ def test_modified(tmp_dir, scm, dvc): result = { "file": { - "old": {"checksum": "8b04d5e3775d298e78455efc5ca404d5", "size": 6}, - "new": {"checksum": "a9f0e61a137d86aa9db53465e0801612", "size": 6}, + "old": {"checksum": _checksum("first"), "size": 6}, + "new": {"checksum": _checksum("second"), "size": 6}, "diff": {"status": "modified", "size": 0}, } } @@ -56,10 +61,27 @@ def test_refs(tmp_dir, scm, dvc): tmp_dir.dvc_gen("file", "second", commit="second version") tmp_dir.dvc_gen("file", "third", commit="third version") - # dvc.diff("HEAD~1") --> (third, second) - # dvc.diff("HEAD~1", "HEAD~2") --> (second, first) - # dvc.diff("missing") --> error - pytest.skip("TODO: define output structure") + HEAD_2 = _checksum("first") + HEAD_1 = _checksum("second") + HEAD = _checksum("third") + + assert dvc.diff("HEAD~1") == { + "file": { + "old": {"checksum": HEAD_1, "size": 5}, + "new": {"checksum": HEAD, "size": 5}, + "diff": {"status": "modified", "size": 0}, + } + } + + assert dvc.diff("HEAD~1", "HEAD~2") == { + "file": { + "old": {"checksum": HEAD_2, "size": 5}, + "new": {"checksum": HEAD_1, "size": 5}, + "diff": {"status": "modified", "size": 0}, + } + } + + pytest.skip('TODO: test dvc.diff("missing")') def test_target(tmp_dir, scm, dvc): @@ -73,9 +95,15 @@ def test_target(tmp_dir, scm, dvc): scm.add(["foo.dvc", "bar.dvc"]) scm.commit("uppercase") - # dvc.diff("HEAD~1", target="foo") - # dvc.diff("HEAD~1", target="missing") --> error - pytest.skip("TODO: define output structure") + assert dvc.diff("HEAD~1", target="foo") == { + "foo": { + "old": {"checksum": _checksum("foo"), "size": 3}, + "new": {"checksum": _checksum("FOO"), "size": 3}, + "diff": {"status": "modified", "size": 0}, + } + } + + assert not dvc.diff("HEAD~1", target="missing") def test_directories(tmp_dir, scm, dvc):