Repository: iterative/dvc

Issue:
metrics diff: cryptic error in --no-scm projects [qa]
```console
λ dvc version
DVC version: 0.92.0
Python version: 3.7.5
Platform: Windows-10-10.0.18362-SP0
Binary: True
Package: exe

λ dvc metrics diff
ERROR: unexpected error - 'NoSCM' object has no attribute 'resolve_rev'
```

Run in a --no-scm DVC project. Compare to `dvc diff`:

```console
λ dvc diff
ERROR: failed to get diff - only supported for Git repositories
```


Comments:

Patch:
diff --git a/dvc/repo/diff.py b/dvc/repo/diff.py
--- a/dvc/repo/diff.py
+++ b/dvc/repo/diff.py
@@ -1,8 +1,6 @@
 import os
 
-from dvc.exceptions import DvcException
 from dvc.repo import locked
-from dvc.scm.git import Git
 from dvc.scm.tree import is_working_tree
 
 
@@ -15,8 +13,6 @@ def diff(self, a_rev="HEAD", b_rev=None):
     the concept of `index`, but it keeps the same interface, thus,
     `dvc diff` would be the same as `dvc diff HEAD`.
     """
-    if type(self.scm) is not Git:
-        raise DvcException("only supported for Git repositories")
 
     def _paths_checksums():
         """
diff --git a/dvc/scm/__init__.py b/dvc/scm/__init__.py
--- a/dvc/scm/__init__.py
+++ b/dvc/scm/__init__.py
@@ -1,13 +1,14 @@
 """Manages source control systems (e.g. Git)."""
 
-from dvc.scm.base import Base
+from dvc.scm.base import Base, NoSCMError
 from dvc.scm.git import Git
 
 
 # Syntactic sugar to signal that this is an actual implementation for a DVC
 # project under no SCM control.
 class NoSCM(Base):
-    pass
+    def __getattr__(self, name):
+        raise NoSCMError
 
 
 def SCM(
diff --git a/dvc/scm/base.py b/dvc/scm/base.py
--- a/dvc/scm/base.py
+++ b/dvc/scm/base.py
@@ -24,6 +24,16 @@ class RevError(SCMError):
     pass
 
 
+class NoSCMError(SCMError):
+    def __init__(self):
+        msg = (
+            "Only supported for Git repositories. If you're "
+            "seeing this error in a Git repo, try updating the DVC "
+            "configuration with `dvc config core.no_scm false`."
+        )
+        super().__init__(msg)
+
+
 class Base(object):
     """Base class for source control management driver implementations."""
 


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
@@ -14,9 +14,11 @@ def digest(text):
 
 
 def test_no_scm(tmp_dir, dvc):
+    from dvc.scm.base import NoSCMError
+
     tmp_dir.dvc_gen("file", "text")
 
-    with pytest.raises(DvcException, match=r"only supported for Git repos"):
+    with pytest.raises(NoSCMError):
         dvc.diff()
 
 
diff --git a/tests/func/test_metrics.py b/tests/func/test_metrics.py
--- a/tests/func/test_metrics.py
+++ b/tests/func/test_metrics.py
@@ -12,6 +12,7 @@
 from dvc.repo import Repo as DvcRepo
 from dvc.repo.metrics.show import NO_METRICS_FILE_AT_REFERENCE_WARNING
 from dvc.utils import relpath
+from dvc.scm.base import NoSCMError
 from tests.basic_env import TestDvcGit
 
 
@@ -1005,3 +1006,13 @@ def test_metrics_diff_deleted_metric(tmp_dir, scm, dvc):
             "a.b.e": {"old": "3", "new": None},
         }
     }
+
+
+def test_metrics_without_scm(tmp_dir, dvc):
+    metrics = {"acc": 0.97, "recall": 0.95}
+    metrics_name = "metrics.json"
+    tmp_dir.gen({metrics_name: json.dumps(metrics)})
+    dvc.add(metrics_name)
+    dvc.metrics.add(metrics_name)
+    with pytest.raises(NoSCMError):
+        dvc.metrics.diff()