Repository: iterative/dvc

Issue:
diff: no need for error when there's no HEAD (new Git repo)
```console
$ dvc version
WARNING: Unable to detect supported link types, as cache directory '.dvc/cache' doesn't exist. It is usually auto-created by commands such as `dvc add/fetch/pull/run/import`, but you could create it manually to enable this check.
DVC version: 0.83.0
Python version: 3.7.6
Platform: Darwin-19.3.0-x86_64-i386-64bit
Binary: False
Package: brew

$ git init
$ dvc init
$ echo foo > data
$ dvc diff
ERROR: failed to get diff - unknown Git revision 'HEAD'                 

Having any troubles? Hit us up at https://dvc.org/support, we are always happy to help!
```

^ It could simply not print anything, I think.

> p.s. also not sure about the need for that `WARNING` in `dvc version`.


Discussion:
p.p.s. it could even default to compare the wokspace vs nothingness in this case? I.e. show all changes (as if there was a `HEAD` that did not have any of the current files).

> I'd check what `git diff` does.

Patch:
diff --git a/dvc/repo/diff.py b/dvc/repo/diff.py
--- a/dvc/repo/diff.py
+++ b/dvc/repo/diff.py
@@ -52,6 +52,9 @@ def _exists(output):
             if _exists(output)
         }
 
+    if self.scm.no_commits:
+        return {}
+
     working_tree = self.tree
     a_tree = self.scm.get_tree(a_rev)
     b_tree = self.scm.get_tree(b_rev) if b_rev else working_tree
diff --git a/dvc/repo/metrics/diff.py b/dvc/repo/metrics/diff.py
--- a/dvc/repo/metrics/diff.py
+++ b/dvc/repo/metrics/diff.py
@@ -14,6 +14,9 @@ def _get_metrics(repo, *args, rev=None, **kwargs):
 
 
 def diff(repo, *args, a_rev=None, b_rev=None, **kwargs):
+    if repo.scm.no_commits:
+        return {}
+
     with_unchanged = kwargs.pop("all", False)
 
     old = _get_metrics(repo, *args, **kwargs, rev=(a_rev or "HEAD"))
diff --git a/dvc/repo/params/diff.py b/dvc/repo/params/diff.py
--- a/dvc/repo/params/diff.py
+++ b/dvc/repo/params/diff.py
@@ -13,6 +13,9 @@ def _get_params(repo, *args, rev=None, **kwargs):
 
 
 def diff(repo, *args, a_rev=None, b_rev=None, **kwargs):
+    if repo.scm.no_commits:
+        return {}
+
     with_unchanged = kwargs.pop("all", False)
 
     old = _get_params(repo, *args, **kwargs, rev=(a_rev or "HEAD"))
diff --git a/dvc/scm/git/__init__.py b/dvc/scm/git/__init__.py
--- a/dvc/scm/git/__init__.py
+++ b/dvc/scm/git/__init__.py
@@ -439,3 +439,7 @@ def _verify_dvc_hooks(self):
         self._verify_hook("post-checkout")
         self._verify_hook("pre-commit")
         self._verify_hook("pre-push")
+
+    @property
+    def no_commits(self):
+        return not self.list_all_commits()


Test patch:
diff --git a/tests/dir_helpers.py b/tests/dir_helpers.py
--- a/tests/dir_helpers.py
+++ b/tests/dir_helpers.py
@@ -63,6 +63,7 @@
     "erepo_dir",
     "git_dir",
     "setup_remote",
+    "git_init",
 ]
 
 
@@ -96,7 +97,7 @@ def init(self, *, scm=False, dvc=False):
         str_path = fspath(self)
 
         if scm:
-            _git_init(str_path)
+            git_init(str_path)
         if dvc:
             self.dvc = Repo.init(
                 str_path, no_scm=not scm and not hasattr(self, "scm")
@@ -243,7 +244,7 @@ def dvc(tmp_dir):
     return tmp_dir.dvc
 
 
-def _git_init(path):
+def git_init(path):
     from git import Repo
     from git.exc import GitCommandNotFound
 
diff --git a/tests/func/metrics/test_diff.py b/tests/func/metrics/test_diff.py
--- a/tests/func/metrics/test_diff.py
+++ b/tests/func/metrics/test_diff.py
@@ -134,3 +134,14 @@ def test_metrics_diff_with_unchanged(tmp_dir, scm, dvc):
             "xyz": {"old": 10, "new": 10, "diff": 0},
         }
     }
+
+
+def test_no_commits(tmp_dir):
+    from dvc.repo import Repo
+    from dvc.scm.git import Git
+    from tests.dir_helpers import git_init
+
+    git_init(".")
+    assert Git().no_commits
+
+    assert Repo.init().metrics.diff() == {}
diff --git a/tests/func/params/test_diff.py b/tests/func/params/test_diff.py
--- a/tests/func/params/test_diff.py
+++ b/tests/func/params/test_diff.py
@@ -119,3 +119,14 @@ def test_pipeline_tracked_params(tmp_dir, scm, dvc, run_copy):
     assert dvc.params.diff(a_rev="HEAD~2") == {
         "params.yaml": {"foo": {"old": "bar", "new": "qux"}}
     }
+
+
+def test_no_commits(tmp_dir):
+    from dvc.repo import Repo
+    from dvc.scm.git import Git
+    from tests.dir_helpers import git_init
+
+    git_init(".")
+    assert Git().no_commits
+
+    assert Repo.init().params.diff() == {}
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
@@ -214,3 +214,14 @@ def test_diff_dirty(tmp_dir, scm, dvc):
 def test_no_changes(tmp_dir, scm, dvc):
     tmp_dir.dvc_gen("file", "first", commit="add a file")
     assert dvc.diff() == {}
+
+
+def test_no_commits(tmp_dir):
+    from dvc.repo import Repo
+    from dvc.scm.git import Git
+    from tests.dir_helpers import git_init
+
+    git_init(".")
+    assert Git().no_commits
+
+    assert Repo.init().diff() == {}
diff --git a/tests/unit/scm/test_git.py b/tests/unit/scm/test_git.py
--- a/tests/unit/scm/test_git.py
+++ b/tests/unit/scm/test_git.py
@@ -70,3 +70,17 @@ def test_is_tracked_unicode(tmp_dir, scm):
     tmp_dir.gen("ṳṋṭṝḁḉḵḗḋ", "untracked")
     assert scm.is_tracked("ṭṝḁḉḵḗḋ")
     assert not scm.is_tracked("ṳṋṭṝḁḉḵḗḋ")
+
+
+def test_no_commits(tmp_dir):
+    from tests.dir_helpers import git_init
+    from dvc.scm.git import Git
+
+    git_init(".")
+    assert Git().no_commits
+
+    tmp_dir.gen("foo", "foo")
+    Git().add(["foo"])
+    Git().commit("foo")
+
+    assert not Git().no_commits