Repository: pypa/setuptools_scm (python)

Issue:
GPG Signature causes: Preparing wheel metadata: finished with status 'error'
**By creating this issue, I want to create awareness about the following behavior:** 

I came across the following Error: pre-commit fails when setting up the black environment.

```
          ...
          File "/private/var/folders/zs/ydvqpbz95pb3_l60_2vk2k0h0000gn/T/pip-build-env-p1l6kgqx/overlay/lib/python3.9/site-packages/setuptools_scm/git.py", line 67, in get_head_date
            return datetime.strptime(date_part, r"%Y-%m-%d").date()
          File "/usr/local/opt/asdf/installs/python/3.9.2/lib/python3.9/_strptime.py", line 568, in _strptime_datetime
            tt, fraction, gmtoff_fraction = _strptime(data_string, format)
          File "/usr/local/opt/asdf/installs/python/3.9.2/lib/python3.9/_strptime.py", line 349, in _strptime
            raise ValueError("time data %r does not match format %r" %
        ValueError: time data 'gpg: Signature made Wed Aug 26 17:50:45 2020 CES' does not match format '%Y-%m-%d'
```
(see [Black Issue 2059](https://github.com/psf/black/issues/2059))

With help from ichard26, I solved the problem by setting `log.showSignature = False` in my gitconfig.




In my optinion it is a good idea to avoid this problem in the future, by making the library independed of showSignature.

Pull request description:
Suppress GPG signature when getting HEAD committer date (#548)
When users have `log.showSignature` enabled, `get_head_date` fails with `ValueError: time data 'gpg: Signature made [...]' does not match format '%Y-%m-%d'`, as observed in #548, #621, psf/black#2059, and so on.

Short of rewriting `get_head_date` to use plumbing commands, we can account for this edge case for git v2.10 and higher (see git/git@369dc4081c836bc17ee1debaf6688eb098359760) with either `git log --no-show-signature` or `git -c log.showSignature=false log`. (The latter has the advantage of not failing due to an unrecognised argument for earlier versions of git.)

This isn't a comprehensive fix, but is hopefully an improvement.

Closes #682, fixes #548.

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index e171cc8..74f0f5a 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,3 +1,8 @@
+v6.4.3
+======
+
+* fix #548: correctly handle parsing the commit timestamp of HEAD when ``log.showSignature`` is set
+
 v6.4.2
 ======
 
diff --git a/src/setuptools_scm/git.py b/src/setuptools_scm/git.py
index 3fe8038..f6933ff 100644
--- a/src/setuptools_scm/git.py
+++ b/src/setuptools_scm/git.py
@@ -69,7 +69,9 @@ class GitWorkdir(Workdir):
         return branch
 
     def get_head_date(self):
-        timestamp, err, ret = self.do_ex("git log -n 1 HEAD --format=%cI")
+        timestamp, err, ret = self.do_ex(
+            "git -c log.showSignature=false log -n 1 HEAD --format=%cI"
+        )
         if ret:
             trace("timestamp err", timestamp, err, ret)
             return
diff --git a/src/setuptools_scm/utils.py b/src/setuptools_scm/utils.py
index 5591384..3c80fe0 100644
--- a/src/setuptools_scm/utils.py
+++ b/src/setuptools_scm/utils.py
@@ -8,6 +8,8 @@ import shlex
 import subprocess
 import sys
 import warnings
+from typing import List
+from typing import Optional
 
 
 DEBUG = bool(os.environ.get("SETUPTOOLS_SCM_DEBUG"))
@@ -118,9 +120,10 @@ def function_has_arg(fn, argname):
     return argname in argspec
 
 
-def has_command(name: str, warn: bool = True) -> bool:
+def has_command(name: str, args: Optional[List[str]] = None, warn: bool = True) -> bool:
     try:
-        p = _popen_pipes([name, "help"], ".")
+        cmd = [name, "help"] if args is None else [name, *args]
+        p = _popen_pipes(cmd, ".")
     except OSError:
         trace(*sys.exc_info())
         res = False


Test patch:
diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml
index 542b221..0778236 100644
--- a/.github/workflows/python-tests.yml
+++ b/.github/workflows/python-tests.yml
@@ -38,6 +38,29 @@ jobs:
           msystem: MINGW64
           install: git mingw-w64-x86_64-python mingw-w64-x86_64-python-setuptools
           update: true
+      - name: Setup GnuPG
+        # At present, the Windows VMs only come with the copy of GnuPG that's bundled
+        # with Git for Windows. If we want to use this version _and_ be able to set
+        # arbitrary GnuPG home directories, then the test would need to figure out when
+        # to convert Windows-style paths into Unix-style paths with cygpath, which is
+        # unreasonable.
+        #
+        # Instead, we'll install a version of GnuPG that can handle Windows-style paths.
+        # However, due to <https://dev.gnupg.org/T5593>, installation fails if the PATH
+        # environment variable is too long. Consequently, we need to shorten PATH to
+        # something minimal before we can install GnuPG. For further details, see
+        # <https://github.com/actions/virtual-environments/issues/2876>.
+        #
+        # Additionally, we'll explicitly set `gpg.program` to ensure Git for Windows
+        # doesn't invoke the bundled GnuPG, otherwise we'll run into
+        # <https://dev.gnupg.org/T5504>. See also: <https://dev.gnupg.org/T3020>.
+        run: |
+          $env:PATH = "C:\Program Files\Git\bin;C:\Windows\system32;C:\Windows;C:\Windows\System32\Wbem;C:\Windows\System32\WindowsPowerShell\v1.0\;C:\ProgramData\Chocolatey\bin"
+          [Environment]::SetEnvironmentVariable("Path", $env:PATH, "Machine")
+          choco install gnupg -y --no-progress
+          echo "C:\Program Files (x86)\gnupg\bin" >> $env:GITHUB_PATH
+          git config --system gpg.program "C:\Program Files (x86)\gnupg\bin\gpg.exe"
+        if: runner.os == 'Windows'
       - run: pip install -U 'setuptools>=45'
         if: matrix.python_version != 'msys2'
       - run: pip install -e .[toml,test]
diff --git a/testing/conftest.py b/testing/conftest.py
index 0621347..6d9a84a 100644
--- a/testing/conftest.py
+++ b/testing/conftest.py
@@ -29,6 +29,7 @@ def pytest_addoption(parser):
 
 class Wd:
     commit_command = None
+    signed_commit_command = None
     add_command = None
 
     def __repr__(self):
@@ -61,19 +62,22 @@ class Wd:
         else:
             return given_reason
 
-    def add_and_commit(self, reason=None):
+    def add_and_commit(self, reason=None, **kwargs):
         self(self.add_command)
-        self.commit(reason)
+        self.commit(reason, **kwargs)
 
-    def commit(self, reason=None):
+    def commit(self, reason=None, signed=False):
         reason = self._reason(reason)
-        self(self.commit_command, reason=reason)
+        self(
+            self.commit_command if not signed else self.signed_commit_command,
+            reason=reason,
+        )
 
-    def commit_testfile(self, reason=None):
+    def commit_testfile(self, reason=None, **kwargs):
         reason = self._reason(reason)
         self.write("test.txt", "test {reason}", reason=reason)
         self(self.add_command)
-        self.commit(reason=reason)
+        self.commit(reason=reason, **kwargs)
 
     def get_version(self, **kw):
         __tracebackhide__ = True
diff --git a/testing/test_git.py b/testing/test_git.py
index b1b42ac..ffb3d3c 100644
--- a/testing/test_git.py
+++ b/testing/test_git.py
@@ -402,3 +402,36 @@ def test_git_getdate_badgit(
     git_wd = git.GitWorkdir(os.fspath(wd.cwd))
     with patch.object(git_wd, "do_ex", Mock(return_value=("%cI", "", 0))):
         assert git_wd.get_head_date() is None
+
+
+@pytest.fixture
+def signed_commit_wd(monkeypatch, wd):
+    if not has_command("gpg", args=["--version"], warn=False):
+        pytest.skip("gpg executable not found")
+
+    wd.write(
+        ".gpg_batch_params",
+        """\
+%no-protection
+%transient-key
+Key-Type: RSA
+Key-Length: 2048
+Name-Real: a test
+Name-Email: test@example.com
+Expire-Date: 0
+""",
+    )
+    monkeypatch.setenv("GNUPGHOME", str(wd.cwd.resolve(strict=True)))
+    wd("gpg --batch --generate-key .gpg_batch_params")
+
+    wd("git config log.showSignature true")
+    wd.signed_commit_command = "git commit -S -m test-{reason}"
+    return wd
+
+
+@pytest.mark.issue("https://github.com/pypa/setuptools_scm/issues/548")
+def test_git_getdate_signed_commit(signed_commit_wd):
+    today = date.today()
+    signed_commit_wd.commit_testfile(signed=True)
+    git_wd = git.GitWorkdir(os.fspath(signed_commit_wd.cwd))
+    assert git_wd.get_head_date() == today