diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index f6e53339ad1..956f9caaf6f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -46,8 +46,19 @@ repos: )$ - repo: https://github.com/s0undt3ch/python-tools-scripts - rev: "0.10.2" + rev: "0.10.4" hooks: + - id: tools + alias: check-changelog-entries + name: Check Changelog Entries + args: + - changelog + - pre-commit-checks + additional_dependencies: + - boto3==1.21.46 + - pyyaml==6.0 + - jinja2==3.1.2 + - packaging==23.0 - id: tools alias: generate-workflows name: Generate GitHub Workflow Templates @@ -1080,15 +1091,6 @@ repos: - requirements/static/ci/tools.in # <---- Tools ----------------------------------------------------------------------------------------------------- - # ----- Local Hooks -----------------------------------------------------------------------------------------------> - - repo: local - hooks: - - id: check-changelog-entries - name: Check Changelog Entries - entry: .pre-commit-hooks/check-changelog-entries.py - language: script - # <---- Local Hooks ------------------------------------------------------------------------------------------------ - # ----- Code Formatting -------------------------------------------------------------------------------------------> - repo: https://github.com/asottile/pyupgrade rev: v2.37.2 diff --git a/.pre-commit-hooks/check-changelog-entries.py b/.pre-commit-hooks/check-changelog-entries.py deleted file mode 100755 index fcf44e800cc..00000000000 --- a/.pre-commit-hooks/check-changelog-entries.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python3 -# pylint: skip-file - -import pathlib -import re -import sys - -CODE_ROOT = pathlib.Path(__file__).resolve().parent.parent -DOCS_PATH = CODE_ROOT / "doc" -TESTS_INTEGRATION_FILES_PATH = CODE_ROOT / "tests" / "integration" / "files" -CHANGELOG_ENTRIES_PATH = CODE_ROOT / "changelog" -CHANGELOG_LIKE_RE = re.compile(r"([\d]+)\.([a-z]+)$") -CHANGELOG_EXTENSIONS = ( - "removed", - "deprecated", - "changed", - "fixed", - "added", - "security", -) -CHANGELOG_ENTRY_RE = re.compile( - r"([\d]+|(CVE|cve)-[\d]{{4}}-[\d]+)\.({})(\.md)?$".format( - "|".join(CHANGELOG_EXTENSIONS) - ) -) - - -def check_changelog_entries(files): - - exitcode = 0 - for entry in files: - path = pathlib.Path(entry).resolve() - # Is it under changelog/ - try: - path.relative_to(CHANGELOG_ENTRIES_PATH) - if path.name == ".keep": - # This is the file we use so git doesn't delete the changelog/ directory - continue - # Is it named properly - if not CHANGELOG_ENTRY_RE.match(path.name): - print( - "The changelog entry '{}' should have one of the following extensions: {}.".format( - path.relative_to(CODE_ROOT), - ", ".join(f"{ext}.md" for ext in CHANGELOG_EXTENSIONS), - ), - file=sys.stderr, - flush=True, - ) - exitcode = 1 - continue - if not path.suffix == ".md": - print( - f"Please rename '{path.relative_to(CODE_ROOT)}' to " - f"'{path.relative_to(CODE_ROOT)}.md'", - file=sys.stderr, - flush=True, - ) - exitcode = 1 - continue - except ValueError: - # No, carry on - pass - # Does it look like a changelog entry - if CHANGELOG_LIKE_RE.match(path.name) and not CHANGELOG_ENTRY_RE.match( - path.name - ): - try: - # Is this under doc/ - path.relative_to(DOCS_PATH) - # Yes, carry on - continue - except ValueError: - # No, resume the check - pass - try: - # Is this under tests/integration/files - path.relative_to(TESTS_INTEGRATION_FILES_PATH) - # Yes, carry on - continue - except ValueError: - # No, resume the check - pass - print( - "The changelog entry '{}' should have one of the following extensions: {}.".format( - path.relative_to(CODE_ROOT), - ", ".join(f"{ext}.md" for ext in CHANGELOG_EXTENSIONS), - ), - file=sys.stderr, - flush=True, - ) - exitcode = 1 - continue - # Is it a changelog entry - if not CHANGELOG_ENTRY_RE.match(path.name): - # No? Carry on - continue - # Is the changelog entry in the right path? - try: - path.relative_to(CHANGELOG_ENTRIES_PATH) - except ValueError: - exitcode = 1 - print( - "The changelog entry '{}' should be placed under '{}/', not '{}'".format( - path.name, - CHANGELOG_ENTRIES_PATH.relative_to(CODE_ROOT), - path.relative_to(CODE_ROOT).parent, - ), - file=sys.stderr, - flush=True, - ) - if not path.suffix == ".md": - print( - f"Please rename '{path.relative_to(CODE_ROOT)}' to " - f"'{path.relative_to(CODE_ROOT)}.md'", - file=sys.stderr, - flush=True, - ) - exitcode = 1 - sys.exit(exitcode) - - -if __name__ == "__main__": - check_changelog_entries(sys.argv[1:]) diff --git a/tools/changelog.py b/tools/changelog.py index 71f6a209b10..fefd9ec8480 100644 --- a/tools/changelog.py +++ b/tools/changelog.py @@ -7,6 +7,8 @@ from __future__ import annotations import datetime import logging import os +import pathlib +import re import subprocess import sys import textwrap @@ -15,6 +17,20 @@ from ptscripts import Context, command_group import tools.utils +REPO_ROOT = pathlib.Path(__file__).resolve().parent.parent +CHANGELOG_LIKE_RE = re.compile(r"([\d]+)\.([a-z]+)$") +CHANGELOG_TYPES = ( + "removed", + "deprecated", + "changed", + "fixed", + "added", + "security", +) +CHANGELOG_ENTRY_RE = re.compile( + r"([\d]+|(CVE|cve)-[\d]{{4}}-[\d]+)\.({})(\.md)?$".format("|".join(CHANGELOG_TYPES)) +) + log = logging.getLogger(__name__) # Define the command group @@ -35,6 +51,103 @@ changelog = command_group( ) +@changelog.command( + name="pre-commit-checks", + arguments={ + "files": { + "nargs": "*", + } + }, +) +def check_changelog_entries(ctx: Context, files: list[pathlib.Path]): + """ + Run pre-commit checks on changelog snippets. + """ + docs_path = REPO_ROOT / "doc" + tests_integration_files_path = REPO_ROOT / "tests" / "integration" / "files" + changelog_entries_path = REPO_ROOT / "changelog" + exitcode = 0 + for entry in files: + path = pathlib.Path(entry).resolve() + # Is it under changelog/ + try: + path.relative_to(changelog_entries_path) + if path.name in (".keep", ".template.jinja"): + # This is the file we use so git doesn't delete the changelog/ directory + continue + # Is it named properly + if not CHANGELOG_ENTRY_RE.match(path.name): + ctx.error( + "The changelog entry '{}' should have one of the following extensions: {}.".format( + path.relative_to(REPO_ROOT), + ", ".join(f"{ext}.md" for ext in CHANGELOG_TYPES), + ), + ) + exitcode = 1 + continue + if path.suffix != ".md": + ctx.error( + f"Please rename '{path.relative_to(REPO_ROOT)}' to " + f"'{path.relative_to(REPO_ROOT)}.md'" + ) + exitcode = 1 + continue + except ValueError: + # No, carry on + pass + # Does it look like a changelog entry + if CHANGELOG_LIKE_RE.match(path.name) and not CHANGELOG_ENTRY_RE.match( + path.name + ): + try: + # Is this under doc/ + path.relative_to(docs_path) + # Yes, carry on + continue + except ValueError: + # No, resume the check + pass + try: + # Is this under tests/integration/files + path.relative_to(tests_integration_files_path) + # Yes, carry on + continue + except ValueError: + # No, resume the check + pass + ctx.error( + "The changelog entry '{}' should have one of the following extensions: {}.".format( + path.relative_to(REPO_ROOT), + ", ".join(f"{ext}.md" for ext in CHANGELOG_TYPES), + ) + ) + exitcode = 1 + continue + # Is it a changelog entry + if not CHANGELOG_ENTRY_RE.match(path.name): + # No? Carry on + continue + # Is the changelog entry in the right path? + try: + path.relative_to(changelog_entries_path) + except ValueError: + exitcode = 1 + ctx.error( + "The changelog entry '{}' should be placed under '{}/', not '{}'".format( + path.name, + changelog_entries_path.relative_to(REPO_ROOT), + path.relative_to(REPO_ROOT).parent, + ) + ) + if path.suffix != ".md": + ctx.error( + f"Please rename '{path.relative_to(REPO_ROOT)}' to " + f"'{path.relative_to(REPO_ROOT)}.md'" + ) + exitcode = 1 + ctx.exit(exitcode) + + def _get_changelog_contents(ctx: Context, version: str): """ Return the full changelog generated by towncrier.