Repository: iterative/dvc

Issue:
diff: Stats reported by dvc not correct
# Description

When tracking a directory using DVC, the stats reported by `dvc diff` are not correct. For example, in a small test repository, the directory `test` is being tracked by DVC. When running `dvc diff` between two commits, the following is reported:

```
$ dvc diff f722af7 91cdc34

dvc diff from f722af74ec372073d04847a3c77ff7e7a154a21b to 91cdc345625ded08cb50f5616f41cee73b198bb6

diff for 'test'
-test with md5 cd5a9abdb72acc541fecb818c8280d5c.dir
+test with md5 3d681d1fe5b39802f663c9a82600d219.dir

0 files not changed, 0 files modified, 0 files added, 0 files deleted, size was increased by 3 Bytes
```

It should say that 1 file was modified, but instead only reports the size change correctly.

# Information about setup

```
$ dvc --version
0.35.5
```
DVC installed via pip.

```
$ python3 --version
Python 3.6.7
```
```
$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 18.04.2 LTS
Release:	18.04
Codename:	bionic
```

diff: Stats reported by dvc not correct
# Description

When tracking a directory using DVC, the stats reported by `dvc diff` are not correct. For example, in a small test repository, the directory `test` is being tracked by DVC. When running `dvc diff` between two commits, the following is reported:

```
$ dvc diff f722af7 91cdc34

dvc diff from f722af74ec372073d04847a3c77ff7e7a154a21b to 91cdc345625ded08cb50f5616f41cee73b198bb6

diff for 'test'
-test with md5 cd5a9abdb72acc541fecb818c8280d5c.dir
+test with md5 3d681d1fe5b39802f663c9a82600d219.dir

0 files not changed, 0 files modified, 0 files added, 0 files deleted, size was increased by 3 Bytes
```

It should say that 1 file was modified, but instead only reports the size change correctly.

# Information about setup

```
$ dvc --version
0.35.5
```
DVC installed via pip.

```
$ python3 --version
Python 3.6.7
```
```
$ lsb_release -a
No LSB modules are available.
Distributor ID:	Ubuntu
Description:	Ubuntu 18.04.2 LTS
Release:	18.04
Codename:	bionic
```



Comments:
Context https://discordapp.com/channels/485586884165107732/485596304961962003/565909315945365544
Will this the correct output

```
$ dvc diff f722af7 91cdc34

dvc diff from f722af74ec372073d04847a3c77ff7e7a154a21b to 91cdc345625ded08cb50f5616f41cee73b198bb6

diff for 'test'
-test with md5 cd5a9abdb72acc541fecb818c8280d5c.dir
+test with md5 3d681d1fe5b39802f663c9a82600d219.dir

0 files not changed, 1 files modified, 0 files added, 0 files deleted, size was increased by 3 Bytes

```

?

Namely, `1 files modified` is the only difference from what it shows currently. 

And `changed`? Are "modified" and "changed" different in this context?
@GildedHonour "0 files _not_ changed". Yes, I think this is the only difference. We need to fix that. Feel free to fix `not changed` part so it's less confusing. We will need to update the docs if needed and write a test for this.
@shcheklein Okay

`1 files changed, 1 files modified, 0 files added, 0 files deleted, size was increased by 3 Bytes`

 
@GildedHonour so, let's imagine we have 10 files in the directory and we change only one of them:

`9 files _not_ changed, 1 files modified, 0 files added, 0 files deleted, size was increased by 3 Bytes`

This is the intended output in this case ^^

Since, I see that the phrase `9 files _not_ changed` is confusing, may be we can change the it a little bit (preserving the semantics of course). 
Alright. I've fixed it I think. 

I'm working on the tests.
there're 2 tests in `func/test_version.py` will fail, they're probably unrelevant to my changes. They may have to do wit my OS -- MacOS. Should I fix them?


1)

        dvc_repo = Repo: '/private/var/folders/qr/0v5z71xn7x503ykyv1j6lkp00000gn/T/dvc-test.28819.vk4jnw5_.DAdbZ4bwgScCFRGEbwNNfJ'
        caplog = <_pytest.logging.LogCaptureFixture object at 0x1258c6210>

            def test_info_in_repo(dvc_repo, caplog):
                assert main(["version"]) == 0
            
                assert re.search(re.compile(r"DVC version: \d+\.\d+\.\d+"), caplog.text)
                assert re.search(re.compile(r"Python version: \d\.\d\.\d"), caplog.text)
                assert re.search(re.compile(r"Platform: .*"), caplog.text)
                assert re.search(re.compile(r"Binary: (True|False)"), caplog.text)
        >       assert re.search(
                    re.compile(r"Filesystem type \(cache directory\): .*"), caplog.text
                )
        E       AssertionError: assert None
        E        +  where None = <function search at 0x10e2b6290>(re.compile('Filesystem type \\(cache directory\\): .*'), 'INFO     dvc.command.version:version.py:65 DVC version: 0.53.1+60159b.mod\n                                          ...                 Platform: Darwin-17.7.0-x86_64-i386-64bit\n                                           Binary: False\n')
        E        +    where <function search at 0x10e2b6290> = re.search
        E        +    and   re.compile('Filesystem type \\(cache directory\\): .*') = <function compile at 0x10e2ea3b0>('Filesystem type \\(cache directory\\): .*')
        E        +      where <function compile at 0x10e2ea3b0> = re.compile
        E        +    and   'INFO     dvc.command.version:version.py:65 DVC version: 0.53.1+60159b.mod\n                                          ...                 Platform: Darwin-17.7.0-x86_64-i386-64bit\n                                           Binary: False\n' = <_pytest.logging.LogCaptureFixture object at 0x1258c6210>.text

        [....]/dvc/tests/func/test_version.py:13: AssertionError



2)


      repo_dir = <tests.basic_env.TestDirFixture object at 0x126578cd0>, caplog = <_pytest.logging.LogCaptureFixture object at 0x1265785d0>

          def test_info_outside_of_repo(repo_dir, caplog):
              assert main(["version"]) == 0
          
              assert re.search(re.compile(r"DVC version: \d+\.\d+\.\d+"), caplog.text)
              assert re.search(re.compile(r"Python version: \d\.\d\.\d"), caplog.text)
              assert re.search(re.compile(r"Platform: .*"), caplog.text)
              assert re.search(re.compile(r"Binary: (True|False)"), caplog.text)
      >       assert re.search(
                  re.compile(r"Filesystem type \(workspace\): .*"), caplog.text
              )
      E       AssertionError: assert None
      E        +  where None = <function search at 0x10e2b6290>(re.compile('Filesystem type \\(workspace\\): .*'), 'INFO     dvc.command.version:version.py:65 DVC version: 0.53.1+60159b.mod\n                                          ...                 Platform: Darwin-17.7.0-x86_64-i386-64bit\n                                           Binary: False\n')
      E        +    where <function search at 0x10e2b6290> = re.search
      E        +    and   re.compile('Filesystem type \\(workspace\\): .*') = <function compile at 0x10e2ea3b0>('Filesystem type \\(workspace\\): .*')
      E        +      where <function compile at 0x10e2ea3b0> = re.compile
      E        +    and   'INFO     dvc.command.version:version.py:65 DVC version: 0.53.1+60159b.mod\n                                          ...                 Platform: Darwin-17.7.0-x86_64-i386-64bit\n                                           Binary: False\n' = <_pytest.logging.LogCaptureFixture object at 0x1265785d0>.text

      [.......]/dvc/tests/func/test_version.py:31: AssertionError

@GildedHonour Those tests were fixed recently on master, please update your branch.
Context https://discordapp.com/channels/485586884165107732/485596304961962003/565909315945365544
Will this the correct output

```
$ dvc diff f722af7 91cdc34

dvc diff from f722af74ec372073d04847a3c77ff7e7a154a21b to 91cdc345625ded08cb50f5616f41cee73b198bb6

diff for 'test'
-test with md5 cd5a9abdb72acc541fecb818c8280d5c.dir
+test with md5 3d681d1fe5b39802f663c9a82600d219.dir

0 files not changed, 1 files modified, 0 files added, 0 files deleted, size was increased by 3 Bytes

```

?

Namely, `1 files modified` is the only difference from what it shows currently. 

And `changed`? Are "modified" and "changed" different in this context?
@GildedHonour "0 files _not_ changed". Yes, I think this is the only difference. We need to fix that. Feel free to fix `not changed` part so it's less confusing. We will need to update the docs if needed and write a test for this.
@shcheklein Okay

`1 files changed, 1 files modified, 0 files added, 0 files deleted, size was increased by 3 Bytes`

 
@GildedHonour so, let's imagine we have 10 files in the directory and we change only one of them:

`9 files _not_ changed, 1 files modified, 0 files added, 0 files deleted, size was increased by 3 Bytes`

This is the intended output in this case ^^

Since, I see that the phrase `9 files _not_ changed` is confusing, may be we can change the it a little bit (preserving the semantics of course). 
Alright. I've fixed it I think. 

I'm working on the tests.
there're 2 tests in `func/test_version.py` will fail, they're probably unrelevant to my changes. They may have to do wit my OS -- MacOS. Should I fix them?


1)

        dvc_repo = Repo: '/private/var/folders/qr/0v5z71xn7x503ykyv1j6lkp00000gn/T/dvc-test.28819.vk4jnw5_.DAdbZ4bwgScCFRGEbwNNfJ'
        caplog = <_pytest.logging.LogCaptureFixture object at 0x1258c6210>

            def test_info_in_repo(dvc_repo, caplog):
                assert main(["version"]) == 0
            
                assert re.search(re.compile(r"DVC version: \d+\.\d+\.\d+"), caplog.text)
                assert re.search(re.compile(r"Python version: \d\.\d\.\d"), caplog.text)
                assert re.search(re.compile(r"Platform: .*"), caplog.text)
                assert re.search(re.compile(r"Binary: (True|False)"), caplog.text)
        >       assert re.search(
                    re.compile(r"Filesystem type \(cache directory\): .*"), caplog.text
                )
        E       AssertionError: assert None
        E        +  where None = <function search at 0x10e2b6290>(re.compile('Filesystem type \\(cache directory\\): .*'), 'INFO     dvc.command.version:version.py:65 DVC version: 0.53.1+60159b.mod\n                                          ...                 Platform: Darwin-17.7.0-x86_64-i386-64bit\n                                           Binary: False\n')
        E        +    where <function search at 0x10e2b6290> = re.search
        E        +    and   re.compile('Filesystem type \\(cache directory\\): .*') = <function compile at 0x10e2ea3b0>('Filesystem type \\(cache directory\\): .*')
        E        +      where <function compile at 0x10e2ea3b0> = re.compile
        E        +    and   'INFO     dvc.command.version:version.py:65 DVC version: 0.53.1+60159b.mod\n                                          ...                 Platform: Darwin-17.7.0-x86_64-i386-64bit\n                                           Binary: False\n' = <_pytest.logging.LogCaptureFixture object at 0x1258c6210>.text

        [....]/dvc/tests/func/test_version.py:13: AssertionError



2)


      repo_dir = <tests.basic_env.TestDirFixture object at 0x126578cd0>, caplog = <_pytest.logging.LogCaptureFixture object at 0x1265785d0>

          def test_info_outside_of_repo(repo_dir, caplog):
              assert main(["version"]) == 0
          
              assert re.search(re.compile(r"DVC version: \d+\.\d+\.\d+"), caplog.text)
              assert re.search(re.compile(r"Python version: \d\.\d\.\d"), caplog.text)
              assert re.search(re.compile(r"Platform: .*"), caplog.text)
              assert re.search(re.compile(r"Binary: (True|False)"), caplog.text)
      >       assert re.search(
                  re.compile(r"Filesystem type \(workspace\): .*"), caplog.text
              )
      E       AssertionError: assert None
      E        +  where None = <function search at 0x10e2b6290>(re.compile('Filesystem type \\(workspace\\): .*'), 'INFO     dvc.command.version:version.py:65 DVC version: 0.53.1+60159b.mod\n                                          ...                 Platform: Darwin-17.7.0-x86_64-i386-64bit\n                                           Binary: False\n')
      E        +    where <function search at 0x10e2b6290> = re.search
      E        +    and   re.compile('Filesystem type \\(workspace\\): .*') = <function compile at 0x10e2ea3b0>('Filesystem type \\(workspace\\): .*')
      E        +      where <function compile at 0x10e2ea3b0> = re.compile
      E        +    and   'INFO     dvc.command.version:version.py:65 DVC version: 0.53.1+60159b.mod\n                                          ...                 Platform: Darwin-17.7.0-x86_64-i386-64bit\n                                           Binary: False\n' = <_pytest.logging.LogCaptureFixture object at 0x1265785d0>.text

      [.......]/dvc/tests/func/test_version.py:31: AssertionError

@GildedHonour Those tests were fixed recently on master, please update your branch.

Patch:
diff --git a/dvc/command/diff.py b/dvc/command/diff.py
--- a/dvc/command/diff.py
+++ b/dvc/command/diff.py
@@ -39,7 +39,7 @@ def _get_md5_string(sign, file_name, checksum):
     def _get_dir_changes(cls, dct):
         engine = inflect.engine()
         changes_msg = (
-            "{} {} not changed, {} {} modified, {} {} added, "
+            "{} {} untouched, {} {} modified, {} {} added, "
             "{} {} deleted, size was {}"
         )
         changes_msg = changes_msg.format(
diff --git a/dvc/repo/diff.py b/dvc/repo/diff.py
--- a/dvc/repo/diff.py
+++ b/dvc/repo/diff.py
@@ -77,7 +77,7 @@ def _modified_files(self, a_entries, b_entries):
     modified_count = 0
     for key in keys:
         if a_entries[key] != b_entries[key]:
-            modified_count
+            modified_count += 1
             diff_size += os.path.getsize(
                 self.cache.local.get(b_entries[key])
             ) - os.path.getsize(self.cache.local.get(a_entries[key]))


Test patch:
diff --git a/tests/func/test_diff.py b/tests/func/test_diff.py
--- a/tests/func/test_diff.py
+++ b/tests/func/test_diff.py
@@ -3,13 +3,8 @@
 import os
 
 from dvc.main import main
-
-
 from dvc.scm.base import FileNotInCommitError
 import dvc.repo.diff as diff
-from dvc.command.diff import CmdDiff
-
-
 from tests.basic_env import TestDvcGit
 
 
@@ -68,26 +63,6 @@ def test(self):
         self.assertEqual(ret, 0)
 
 
-class TestDiffCmdMessage(TestDiff):
-    maxDiff = None
-
-    def test(self):
-        msg = CmdDiff._show(self.test_dct)
-        test_msg = (
-            "dvc diff from {0} to {1}\n\n"
-            "diff for '{2}'\n"
-            "+{2} with md5 {3}\n\n"
-            "added file with size 13 Bytes"
-        )
-        test_msg = test_msg.format(
-            self.test_dct[diff.DIFF_A_REF],
-            self.test_dct[diff.DIFF_B_REF],
-            self.test_dct[diff.DIFF_LIST][0][diff.DIFF_TARGET],
-            self.test_dct[diff.DIFF_LIST][0][diff.DIFF_NEW_CHECKSUM],
-        )
-        self.assertEqual(test_msg, msg)
-
-
 class TestDiffDir(TestDvcGit):
     def setUp(self):
         super(TestDiffDir, self).setUp()
@@ -192,3 +167,109 @@ def setUp(self):
     def test(self):
         with self.assertRaises(FileNotInCommitError):
             self.dvc.diff(self.a_ref, target=self.unknown_file)
+
+
+class TestDiffModifiedFile(TestDiff):
+    maxDiff = None
+
+    def setUp(self):
+        super(TestDiffModifiedFile, self).setUp()
+
+        self.old_checksum = _get_checksum(self.dvc, self.new_file)
+        self.new_file_content = "new_test_file_bigger_content_123456789"
+        self.diff_len = len(self.new_file) + len(self.new_file_content)
+        self.create(self.new_file, self.new_file_content)
+        self.dvc.add(self.new_file)
+        self.git.index.add([self.new_file + ".dvc"])
+        self.git.index.commit("change new_file content to be bigger")
+        self.new_checksum = _get_checksum(self.dvc, self.new_file)
+        self.b_ref = self.git.git.rev_parse(self.git.head.commit, short=True)
+
+    def test(self):
+        result = self.dvc.diff(
+            self.a_ref, b_ref=self.b_ref, target=self.new_file
+        )
+        test_dct = {
+            diff.DIFF_A_REF: self.git.git.rev_parse(self.a_ref, short=True),
+            diff.DIFF_B_REF: self.git.git.rev_parse(self.b_ref, short=True),
+            diff.DIFF_LIST: [
+                {
+                    diff.DIFF_NEW_CHECKSUM: self.new_checksum,
+                    diff.DIFF_NEW_FILE: self.new_file,
+                    diff.DIFF_TARGET: self.new_file,
+                    diff.DIFF_SIZE: self.diff_len,
+                }
+            ],
+        }
+        self.assertEqual(test_dct, result)
+
+
+class TestDiffDirWithFile(TestDiffDir):
+    maxDiff = None
+
+    def setUp(self):
+        super(TestDiffDirWithFile, self).setUp()
+
+        self.a_ref = self.git.git.rev_parse(self.git.head.commit, short=True)
+        self.old_checksum = _get_checksum(self.dvc, self.DATA_DIR)
+        self.new_file_content = "new_test_file_bigger_content_123456789"
+        self.diff_len = len(self.new_file_content)
+        self.create(self.new_file, self.new_file_content)
+        self.dvc.add(self.DATA_DIR)
+        self.git.index.add([self.DATA_DIR + ".dvc"])
+        self.git.index.commit(message="modify file in the data dir")
+        self.new_checksum = _get_checksum(self.dvc, self.DATA_DIR)
+        self.b_ref = self.git.git.rev_parse(self.git.head.commit, short=True)
+
+    def test(self):
+        result = self.dvc.diff(self.a_ref, target=self.DATA_DIR)
+        test_dct = {
+            diff.DIFF_A_REF: self.git.git.rev_parse(self.a_ref, short=True),
+            diff.DIFF_B_REF: self.git.git.rev_parse(self.b_ref, short=True),
+            diff.DIFF_LIST: [
+                {
+                    diff.DIFF_IDENT: 2,
+                    diff.DIFF_CHANGE: 1,
+                    diff.DIFF_DEL: 0,
+                    diff.DIFF_MOVE: 0,
+                    diff.DIFF_NEW: 0,
+                    diff.DIFF_IS_DIR: True,
+                    diff.DIFF_TARGET: self.DATA_DIR,
+                    diff.DIFF_NEW_FILE: self.DATA_DIR,
+                    diff.DIFF_NEW_CHECKSUM: self.new_checksum,
+                    diff.DIFF_OLD_FILE: self.DATA_DIR,
+                    diff.DIFF_OLD_CHECKSUM: self.old_checksum,
+                    diff.DIFF_SIZE: self.diff_len,
+                }
+            ],
+        }
+        self.assertEqual(test_dct, result)
+
+
+class TestDiffCmdMessage(TestDiff):
+    maxDiff = None
+
+    def test(self):
+        ret = main(
+            [
+                "diff",
+                self.test_dct[diff.DIFF_A_REF],
+                self.test_dct[diff.DIFF_B_REF],
+            ]
+        )
+        self.assertEqual(ret, 0)
+
+        msg1 = "dvc diff from {0} to {1}".format(
+            self.git.git.rev_parse(self.test_dct[diff.DIFF_A_REF], short=True),
+            self.git.git.rev_parse(self.test_dct[diff.DIFF_B_REF], short=True),
+        )
+        msg2 = "diff for '{0}'".format(
+            self.test_dct[diff.DIFF_LIST][0][diff.DIFF_TARGET]
+        )
+        msg3 = "+{0} with md5 {1}".format(
+            self.test_dct[diff.DIFF_LIST][0][diff.DIFF_TARGET],
+            self.test_dct[diff.DIFF_LIST][0][diff.DIFF_NEW_CHECKSUM],
+        )
+        msg4 = "added file with size 13 Bytes"
+        for m in [msg1, msg2, msg3, msg4]:
+            assert m in self._caplog.text