From 096da69937a14bc0dfa46b34831d20238c98fe35 Mon Sep 17 00:00:00 2001 From: Tim Hsiung Date: Sat, 9 May 2026 17:35:32 +0800 Subject: [PATCH] test(conftest): make GPG-signing fixture work on Windows Two changes to the `tmp_commitizen_project_with_gpg` fixture and its helper so that the GPG-related bump tests run reliably on Windows hosts where `Gpg4win` and `Git for Windows` ship separate gpg binaries and keyrings: * `_get_gpg_keyid` now parses `gpg --with-colons` output instead of relying on a regex over the human-friendly layout. The regex required LF line endings, which gpg does not produce on Windows (CRLF), so it always returned `None` and the fixture asserted out. * The fixture pins `git config gpg.program` to the same gpg binary it used to create the test key (resolved via `shutil.which`). Otherwise `git commit -S` invokes the gpg shipped with Git for Windows, which has a different keyring and reports `gpg: skipped : No secret key`. Behaviour on Linux / macOS is unchanged because `shutil.which` and `--with-colons` work identically there. The codespell ignore list is extended with `fpr` so the gpg colon output prefix used in the parser does not trip the spelling check. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- pyproject.toml | 2 +- tests/conftest.py | 32 +++++++++++++++++++++++++------- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 85957e0b8..6e4efbd20 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -268,7 +268,7 @@ ignore_missing_imports = true # Ref: https://github.com/codespell-project/codespell#using-a-config-file skip = '.git*,*.svg,*.lock' check-hidden = true -ignore-words-list = 'asend' +ignore-words-list = 'asend,fpr' [tool.poe] executor.type = "uv" diff --git a/tests/conftest.py b/tests/conftest.py index 9b29d2a19..bdb4f9e87 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,7 +1,7 @@ from __future__ import annotations import os -import re +import shutil import subprocess import sys import tempfile @@ -123,12 +123,22 @@ def _initial( def _get_gpg_keyid(signer_mail): - _new_key = cmd.run(["gpg", "--list-secret-keys", signer_mail]) - _m = re.search( - r"[a-zA-Z0-9 \[\]-_]*\n[ ]*([0-9A-Za-z]*)\n[\na-zA-Z0-9 \[\]-_<>@]*", - _new_key.out, - ) - return _m.group(1) if _m else None + """Return the primary fingerprint of the first secret key matching ``signer_mail``. + + Uses the stable ``--with-colons`` machine-readable format so we don't rely + on ``gpg``'s human-friendly layout, which differs between platforms (notably + CRLF on Windows). + """ + _new_key = cmd.run(["gpg", "--with-colons", "--list-secret-keys", signer_mail]) + in_sec = False + for line in _new_key.out.splitlines(): + if line.startswith("sec:"): + in_sec = True + elif in_sec and line.startswith("fpr:"): + fields = line.split(":") + # Field index 9 holds the fingerprint per gpg(1) DETAILS. + return fields[9] if len(fields) > 9 and fields[9] else None + return None @pytest.fixture @@ -140,6 +150,12 @@ def tmp_commitizen_project_with_gpg(tmp_commitizen_project): if os.name != "nt": os.environ["GNUPGHOME"] = gpg_home.name # tempdir = temp keyring + # Resolve the absolute path to the gpg binary used by this fixture so that + # git invokes the same one (and therefore the same keyring). This matters + # on Windows where Git for Windows ships its own gpg binary that has a + # different keyring than a system-wide GnuPG/Gpg4win install. + gpg_binary = shutil.which("gpg") + try: # create a key (a keyring will be generated within GPUPGHOME) subprocess.run( @@ -161,6 +177,8 @@ def tmp_commitizen_project_with_gpg(tmp_commitizen_project): # configure git to use gpg signing cmd.run(["git", "config", "commit.gpgsign", "true"]) cmd.run(["git", "config", "user.signingkey", key_id]) + if gpg_binary: + cmd.run(["git", "config", "gpg.program", gpg_binary]) yield tmp_commitizen_project finally: