From 2602e5b6770c3e3fb7ccac013cc603b3e124a3eb Mon Sep 17 00:00:00 2001 From: Ilan Gold Date: Mon, 18 Nov 2024 13:46:12 +0100 Subject: [PATCH] (feat): restrict release branches in automation (#1765) * (fix): no patch release on main for automation * (fix): use non-release-regex * (fix): `msg` format * (fix): string business * Use argparse machinery (#1770) Co-authored-by: Ilan Gold --------- Co-authored-by: Philipp A. --- ci/scripts/towncrier_automation.py | 52 +++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/ci/scripts/towncrier_automation.py b/ci/scripts/towncrier_automation.py index 17fd10902..b49ba340e 100755 --- a/ci/scripts/towncrier_automation.py +++ b/ci/scripts/towncrier_automation.py @@ -2,7 +2,9 @@ from __future__ import annotations import argparse +import re import subprocess +from functools import cache from typing import TYPE_CHECKING from packaging.version import Version @@ -11,8 +13,33 @@ from collections.abc import Sequence +class BumpVersion(Version): + def __init__(self, version: str) -> None: + super().__init__(version) + + if len(self.release) != 3: + msg = f"{version} must contain major, minor, and patch version." + raise argparse.ArgumentTypeError(msg) + + base_branch = get_base_branch() + patch_branch_pattern = re.compile(r"\d+\.\d+\.x") + if self.micro != 0 and not patch_branch_pattern.fullmatch(base_branch): + msg = ( + f"{version} is a patch release, but " + f"you are trying to release from a non-patch release branch: {base_branch}." + ) + raise argparse.ArgumentTypeError(msg) + + if self.micro == 0 and base_branch != "main": + msg = ( + f"{version} is a minor or major release, " + f"but you are trying to release not from main: {base_branch}." + ) + raise argparse.ArgumentTypeError(msg) + + class Args(argparse.Namespace): - version: str + version: BumpVersion dry_run: bool @@ -28,7 +55,7 @@ def parse_args(argv: Sequence[str] | None = None) -> Args: ) parser.add_argument( "version", - type=str, + type=BumpVersion, help=( "The new version for the release must have at least three parts, like `major.minor.patch` and no `major.minor`. " "It can have a suffix like `major.minor.patch.dev0` or `major.minor.0rc1`." @@ -40,10 +67,6 @@ def parse_args(argv: Sequence[str] | None = None) -> Args: action="store_true", ) args = parser.parse_args(argv, Args()) - # validate the version - if len(Version(args.version).release) != 3: - msg = f"Version argument {args.version} must contain major, minor, and patch version." - raise ValueError(msg) return args @@ -56,12 +79,7 @@ def main(argv: Sequence[str] | None = None) -> None: ) # Check if we are on the main branch to know if we need to backport - base_branch = subprocess.run( - ["git", "rev-parse", "--abbrev-ref", "HEAD"], - capture_output=True, - text=True, - check=True, - ).stdout.strip() + base_branch = get_base_branch() pr_description = "" if base_branch == "main" else "@meeseeksdev backport to main" branch_name = f"release_notes_{args.version}" @@ -104,5 +122,15 @@ def main(argv: Sequence[str] | None = None) -> None: print("Dry run, not merging") +@cache +def get_base_branch(): + return subprocess.run( + ["git", "rev-parse", "--abbrev-ref", "HEAD"], + capture_output=True, + text=True, + check=True, + ).stdout.strip() + + if __name__ == "__main__": main()