[PATCH] [pre-commit] Don't allow pre-release revisions

Tom de Vries <[email protected]>
Newsgroups gmane.comp.gdb.patches
Message-ID <[email protected]>
Pre-commit has a convenient autoupdate command.  The default behavior is:
update to the latest tagged version.

For most tools this is fine, and not too frequent.

Isort however also tags pre-releases, so that has resulted in more frequent
updates:
...
$ git log .pre-commit-config.yaml | grep "Bump isort"
    [pre-commit] Bump isort to 9.0.0b1
    [pre-commit] Bump isort to 9.0.0a3
    [pre-commit] Bump isort to 9.0.0a2
    [pre-commit] Bump isort to 8.0.1
    [pre-commit] Bump isort to 8.0.0
...

Add a script gdb/contrib/pre-commit.py that for --config-check checks the
revision numbers of all repos, and errors out when finding something like
9.0.0b1:
...
$ ./gdb/contrib/pre-commit.py --config-check
Revision 9.0.0b1 for repo https://github.com/pycqa/isort not allowed.
...

Add a corresponding pre-commit check, and revert isort back to the last stable
release: 8.0.1, to make the check pass.

Consequently, pre-commit autoupdate now breaks the pre-commit check:
...
$ pre-commit autoupdate
[https://github.com/psf/black-pre-commit-mirror] already up to date!
[https://github.com/pycqa/flake8] already up to date!
[https://github.com/pycqa/isort] updating 8.0.1 -> 9.0.0b2
[https://github.com/codespell-project/codespell] already up to date!
[https://github.com/nmoroze/tclint] already up to date!
[https://github.com/adrienverge/yamllint.git] already up to date!
$ ./gdb/contrib/pre-commit.py --config-check
Revision 9.0.0b2 for repo https://github.com/pycqa/isort not allowed.
...
so add gdb/contrib/pre-commit.py --autoupdate that can be used instead:
...
$ ./gdb/contrib/pre-commit.py --autoupdate
[https://github.com/psf/black-pre-commit-mirror] already up to date!
[https://github.com/pycqa/flake8] already up to date!
[https://github.com/pycqa/isort] updating 8.0.1 -> 9.0.0b2
Revision 9.0.0b2 for repo https://github.com/pycqa/isort not allowed.
[https://github.com/codespell-project/codespell] already up to date!
[https://github.com/nmoroze/tclint] already up to date!
[https://github.com/adrienverge/yamllint.git] already up to date!
...

The script could be extended to check that frozen revs stay frozen, but that's
currently not needed.

Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=34393
---
 .pre-commit-config.yaml   |   9 ++-
 gdb/contrib/pre-commit.py | 154 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 162 insertions(+), 1 deletion(-)
 create mode 100755 gdb/contrib/pre-commit.py

diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 3cccec45ef7..7b97b9a0ce8 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -70,7 +70,7 @@ repos:
         files: *gdb_python_files
         args: [--config, gdb/setup.cfg]
   - repo: https://github.com/pycqa/isort
-    rev: 9.0.0b1
+    rev: 8.0.1
     hooks:
       - id: isort
         types_or: *gdb_python_types
@@ -147,6 +147,13 @@ repos:
         language: unsupported_script
         entry: gdb/contrib/check-file-mode.sh
         files: *gdb_files
+      - id: &id5 pre-commit-config-check
+        name: *id5
+        language: python
+        entry: gdb/contrib/pre-commit.py
+        args: [--config-check]
+        additional_dependencies: ["pyyaml"]
+        files: '^\.pre-commit-config.yaml$'
 
 # Local Variables:
 # indent-tabs-mode: nil
diff --git a/gdb/contrib/pre-commit.py b/gdb/contrib/pre-commit.py
new file mode 100755
index 00000000000..9ab746c7355
--- /dev/null
+++ b/gdb/contrib/pre-commit.py
@@ -0,0 +1,154 @@
+#!/usr/bin/env python3
+
+# Copyright (C) 2026 Free Software Foundation, Inc.
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+import re
+import subprocess
+import sys
+
+import yaml
+
+
+def get_repos(cfg):
+    with open(cfg, "r") as f:
+        data = yaml.safe_load(f)
+        repos = data.get("repos")
+        if not repos:
+            raise RuntimeError("repos missing")
+        return repos
+
+
+def config_check_repo(repo):
+    name = repo.get("repo")
+    if not name:
+        raise RuntimeError("no repo")
+    if name == "local":
+        # Skip local repo, there's no revision to check.
+        return True
+
+    # Get the revision.
+    rev = repo["rev"]
+    if not rev or rev == "":
+        raise RuntimeError("empty revision")
+
+    # Normalize revision: skip 'v' prefix.
+    if rev[0].lower() == "v":
+        rev = rev[1:]
+
+    # Check version number.  Don't allow pre-releases like 9.0.0b1.
+    # We currently only need to support x.y.z, but that could change.
+    if not re.fullmatch(r"\d+[.]\d+[.]\d+", rev):
+        print("Revision %s for repo %s not allowed." % (rev, name))
+        return False
+
+    return True
+
+
+def config_check(cfg):
+    for repo in get_repos(cfg):
+        if not config_check_repo(repo):
+            sys.exit(1)
+
+
+def run_cmd(cmd, **kwargs):
+    res = subprocess.run(cmd, **kwargs)
+    if res.returncode != 0:
+        raise RuntimeError(
+            "command %s failed with exit status %s" % (cmd, res.returncode)
+        )
+    return res
+
+
+def autoupdate_repo(cfg, repo):
+    name = repo["repo"]
+    if name == "local":
+        # Skip local repo, there's no revision to update.
+        return
+
+    cmd = ["pre-commit", "autoupdate", "--repo", name]
+    run_cmd(cmd)
+
+    cmd = ["git", "status", "--porcelain", cfg]
+    res = run_cmd(cmd, capture_output=True, text=True)
+    if res.stdout == "":
+        # No autoupdate changes.
+        return
+
+    rev = repo["rev"]
+
+    # Config has changed, refresh repo.
+    found = False
+    for new_repo in get_repos(cfg):
+        if new_repo["repo"] == name:
+            found = True
+            break
+    if not found:
+        raise RuntimeError("Repo not found in updated %s" % cfg)
+    repo = new_repo
+
+    new_rev = repo["rev"]
+
+    if not config_check_repo(repo):
+        # Reject autoupdate for this repo.
+        cmd = ["git", "checkout", "-f", cfg]
+        # Capture and ignore output.
+        run_cmd(cmd, capture_output=True, text=True)
+        return
+
+    # Commit autoupdate for this repo.
+    try:
+        name_for_msg = repo["hooks"][0]["id"]
+    except (KeyError, IndexError):
+        name_for_msg = name
+    msg = "Update %s: %s -> %s" % (name_for_msg, rev, new_rev)
+    cmd = ["git", "commit", "-m", msg, cfg]
+    run_cmd(cmd)
+
+
+def autoupdate(cfg):
+    for repo in get_repos(cfg):
+        autoupdate_repo(cfg, repo)
+
+
+def usage():
+    print("Usage: pre-commit.py --config-check [<file>]")
+    print("                     --autoupdate")
+    sys.exit(1)
+
+
+def main():
+    if len(sys.argv) < 2:
+        usage()
+
+    cfg = ".pre-commit-config.yaml"
+
+    if sys.argv[1] == "--config-check":
+        if len(sys.argv) not in [2, 3]:
+            usage()
+        if len(sys.argv) == 3:
+            cfg = sys.argv[2]
+        config_check(cfg)
+        return
+
+    if sys.argv[1] == "--autoupdate":
+        if len(sys.argv) != 2:
+            usage()
+        autoupdate(cfg)
+        return
+
+    usage()
+
+
+main()

base-commit: 1686e21559a7812ebbc05f57372cc30880768bf7
-- 
2.51.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.