Repository: pypa/setuptools_scm

Issue:
lookup failed because git is not installed
https://github.com/pypa/setuptools_scm/blob/7f6c9cfa52a47f4df81e753965bc37ca430e8e21/src/setuptools_scm/__init__.py#L105-L114

While git is not installed, it will also show the follow message.

```
"setuptools-scm was unable to detect version for %r.\n\n"
        "Make sure you're either building from a fully intact git repository "
        "or PyPI tarballs. Most other sources (such as GitHub's tarballs, a "
        "git checkout without the .git folder) don't contain the necessary "
        "metadata and will not work.\n\n"
        "For example, if you're using pip, instead of "
        "https://github.com/user/proj/archive/master.zip "
        "use git+https://github.com/user/proj.git#egg=proj" % config.absolute_root
```

It will be great if the message also indicate the fact that not only .git folder, git is also required.

While using tool like docker multi-stage build or cloudbuild to minimize the image, it is common to work in an env without git pre-installed. 

Discussion:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index c570357..83666ae 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,12 @@
-v4.2.0
+v5.0.0
 ======
 
+
+Breaking changes:
+* fix #339: strict errors on missing scms when  parsing a scm dir to avoid false version lookups
+
+Bugfixes:
+
 * fix #352: add support for generally ignoring specific vcs roots
 * fix #471: better error for version bump failing on complex but accepted tag
 * fix #479: raise indicative error when tags carry non-parsable information
diff --git a/src/setuptools_scm/git.py b/src/setuptools_scm/git.py
index 76be436..4fd5d49 100644
--- a/src/setuptools_scm/git.py
+++ b/src/setuptools_scm/git.py
@@ -1,5 +1,5 @@
 from .config import Configuration
-from .utils import do_ex, trace, has_command
+from .utils import do_ex, trace, require_command
 from .version import meta
 
 from os.path import isfile, join
@@ -92,8 +92,7 @@ def parse(
     if not config:
         config = Configuration(root=root)
 
-    if not has_command("git"):
-        return
+    require_command("git")
 
     wd = GitWorkdir.from_potential_worktree(config.absolute_root)
     if wd is None:
diff --git a/src/setuptools_scm/hg.py b/src/setuptools_scm/hg.py
index d699d45..2ac9141 100644
--- a/src/setuptools_scm/hg.py
+++ b/src/setuptools_scm/hg.py
@@ -1,6 +1,6 @@
 import os
 from .config import Configuration
-from .utils import do, trace, data_from_mime, has_command
+from .utils import do, trace, data_from_mime, require_command
 from .version import meta, tags_to_versions
 
 
@@ -36,8 +36,7 @@ def parse(root, config=None):
     if not config:
         config = Configuration(root=root)
 
-    if not has_command("hg"):
-        return
+    require_command("hg")
     identity_data = do("hg id -i -b -t", config.absolute_root).split()
     if not identity_data:
         return
diff --git a/src/setuptools_scm/utils.py b/src/setuptools_scm/utils.py
index 15ca83e..413e98a 100644
--- a/src/setuptools_scm/utils.py
+++ b/src/setuptools_scm/utils.py
@@ -132,7 +132,7 @@ def function_has_arg(fn, argname):
     return argname in argspec
 
 
-def has_command(name):
+def has_command(name, warn=True):
     try:
         p = _popen_pipes([name, "help"], ".")
     except OSError:
@@ -141,6 +141,11 @@ def has_command(name):
     else:
         p.communicate()
         res = not p.returncode
-    if not res:
-        warnings.warn("%r was not found" % name)
+    if not res and warn:
+        warnings.warn("%r was not found" % name, category=RuntimeWarning)
     return res
+
+
+def require_command(name):
+    if not has_command(name, warn=False):
+        raise EnvironmentError("%r was not found" % name)


Test patch:
diff --git a/testing/test_git.py b/testing/test_git.py
index 6f5246c..1b57fed 100644
--- a/testing/test_git.py
+++ b/testing/test_git.py
@@ -7,7 +7,6 @@ import pytest
 from datetime import datetime
 from os.path import join as opj
 from setuptools_scm.file_finder_git import git_find_files
-import warnings
 
 
 skip_if_win_27 = pytest.mark.skipif(
@@ -16,10 +15,9 @@ skip_if_win_27 = pytest.mark.skipif(
 )
 
 
-with warnings.catch_warnings():
-    warnings.filterwarnings("ignore")
-    if not has_command("git"):
-        pytestmark = pytest.mark.skip(reason="git executable not found")
+pytestmark = pytest.mark.skipif(
+    not has_command("git", warn=False), reason="git executable not found"
+)
 
 
 @pytest.fixture
@@ -59,6 +57,12 @@ setup(use_scm_version={"root": "../..",
     assert res == "0.1.dev0"
 
 
+def test_git_gone(wd, monkeypatch):
+    monkeypatch.setenv("PATH", str(wd.cwd / "not-existing"))
+    with pytest.raises(EnvironmentError, match="'git' was not found"):
+        git.parse(str(wd.cwd), git.DEFAULT_DESCRIBE)
+
+
 @pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/298")
 @pytest.mark.issue(403)
 def test_file_finder_no_history(wd, caplog):
diff --git a/testing/test_integration.py b/testing/test_integration.py
index 4564897..446aac0 100644
--- a/testing/test_integration.py
+++ b/testing/test_integration.py
@@ -8,11 +8,7 @@ from setuptools_scm import PRETEND_KEY, PRETEND_KEY_NAMED
 
 @pytest.fixture
 def wd(wd):
-    try:
-        wd("git init")
-    except OSError:
-        pytest.skip("git executable not found")
-
+    wd("git init")
     wd("git config user.email test@example.com")
     wd('git config user.name "a test"')
     wd.add_command = "git add ."
diff --git a/testing/test_mercurial.py b/testing/test_mercurial.py
index 815ca00..265e207 100644
--- a/testing/test_mercurial.py
+++ b/testing/test_mercurial.py
@@ -4,13 +4,11 @@ from setuptools_scm import integration
 from setuptools_scm.config import Configuration
 from setuptools_scm.utils import has_command
 import pytest
-import warnings
 
 
-with warnings.catch_warnings():
-    warnings.filterwarnings("ignore")
-    if not has_command("hg"):
-        pytestmark = pytest.mark.skip(reason="hg executable not found")
+pytestmark = pytest.mark.skipif(
+    not has_command("hg", warn=False), reason="hg executable not found"
+)
 
 
 @pytest.fixture
@@ -46,6 +44,12 @@ def test_archival_to_version(expected, data):
     )
 
 
+def test_hg_gone(wd, monkeypatch):
+    monkeypatch.setenv("PATH", str(wd.cwd / "not-existing"))
+    with pytest.raises(EnvironmentError, match="'hg' was not found"):
+        parse(str(wd.cwd))
+
+
 def test_find_files_stop_at_root_hg(wd, monkeypatch):
     wd.commit_testfile()
     project = wd.cwd / "project"