diff --git a/CI/release.py b/CI/release.py index 1bd9c55a43c..07cf0bd5b9e 100755 --- a/CI/release.py +++ b/CI/release.py @@ -15,11 +15,14 @@ import aiohttp from gidgethub.aiohttp import GitHubAPI from gidgethub import InvalidField +from semantic_release.enums import LevelBump +from semantic_release.version import Version +from semantic_release.commit_parser.angular import ( + AngularCommitParser, + AngularParserOptions, +) +from semantic_release.commit_parser.token import ParseError, ParseResult import gidgethub -from semantic_release.history import angular_parser, get_new_version -from semantic_release.errors import UnknownCommitMessageStyleError -from semantic_release.history.logs import LEVELS -from semantic_release.history.parser_helpers import ParsedCommit import sh from dotenv import load_dotenv import functools @@ -68,8 +71,13 @@ def __str__(self): message = self.message.split("\n")[0] return f"Commit(sha='{self.sha[:8]}', message='{message}')" + # needed for semantic_release duck typing + @property + def hexsha(self): + return self.sha -_default_parser = angular_parser + +_default_parser = AngularCommitParser(AngularParserOptions()) def evaluate_version_bump( @@ -85,16 +93,17 @@ def evaluate_version_bump( for commit in commits: commit_count += 1 - try: - message = commit_parser(commit.message) - changes.append(message.bump) - except UnknownCommitMessageStyleError: + + message: ParseResult = commit_parser.parse(commit) + if isinstance(message, ParseError): print("Unknown commit message style!") + else: + changes.append(message.bump) if changes: level = max(changes) - if level in LEVELS: - bump = LEVELS[level] + if level in LevelBump: + bump = level else: print(f"Unknown bump level {level}") @@ -108,26 +117,27 @@ def generate_changelog(commits, commit_parser=_default_parser) -> dict: changes: dict = {"breaking": []} for commit in commits: - try: - message: ParsedCommit = commit_parser(commit.message) - if message.type not in changes: - changes[message.type] = list() + message: ParseResult = commit_parser.parse(commit) - capital_message = ( - message.descriptions[0][0].upper() + message.descriptions[0][1:] - ) - changes[message.type].append((commit.sha, capital_message, commit.author)) - - if message.breaking_descriptions: - for paragraph in message.breaking_descriptions: - changes["breaking"].append((commit.sha, paragraph, commit.author)) - elif message.bump == 3: - changes["breaking"].append( - (commit.sha, message.descriptions[0], commit.author) - ) - - except UnknownCommitMessageStyleError: + if isinstance(message, ParseError): print("Unknown commit message style!") + continue + + if message.type not in changes: + changes[message.type] = list() + + capital_message = ( + message.descriptions[0][0].upper() + message.descriptions[0][1:] + ) + changes[message.type].append((commit.sha, capital_message, commit.author)) + + if message.breaking_descriptions: + for paragraph in message.breaking_descriptions: + changes["breaking"].append((commit.sha, paragraph, commit.author)) + elif message.bump == 3: + changes["breaking"].append( + (commit.sha, message.descriptions[0], commit.author) + ) return changes @@ -189,14 +199,16 @@ async def get_parsed_commit_range( if commit_hash == end: break + commit = Commit(commit_hash, commit_message, item["author"]["login"]) + invalid_message = False - try: - _default_parser(commit_message) - # if this succeeds, do nothing - except UnknownCommitMessageStyleError: + message: ParseResult = _default_parser.parse(commit) + + if isinstance(message, ParseError): print("Unknown commit message style!") if not commit_message.startswith("Merge"): invalid_message = True + if ( (invalid_message or edit) and sys.stdout.isatty() @@ -206,7 +218,6 @@ async def get_parsed_commit_range( commit_message = typer.edit(commit_message) _default_parser(commit_message) - commit = Commit(commit_hash, commit_message, item["author"]["login"]) commits.append(commit) if invalid_message: @@ -227,6 +238,7 @@ async def get_parsed_commit_range( @make_sync async def make_release( token: str = typer.Argument(..., envvar="GH_TOKEN"), + force_next_version: Optional[str] = typer.Option(None, "--next-version"), draft: bool = True, dry_run: bool = False, edit: bool = False, @@ -255,7 +267,12 @@ async def make_release( if bump is None: print("-> nothing to do") return - next_version = get_new_version(current_version, bump) + + current_version_obj = Version(*(map(int, current_version.split(".")))) + next_version_obj = current_version_obj.bump(bump) + next_version = f"{next_version_obj.major}.{next_version_obj.minor}.{next_version_obj.patch}" + if force_next_version is not None: + next_version = force_next_version print("next version:", next_version) next_tag = f"v{next_version}" @@ -265,18 +282,9 @@ async def make_release( print(md) if not dry_run: - version_file.write_text(next_version) - git.add(version_file) + execute_bump(next_version) - zenodo_file = Path(".zenodo.json") - update_zenodo(zenodo_file, repo, next_version) - git.add(zenodo_file) - - citation_file = Path("CITATION.cff") - update_citation(citation_file, next_version) - git.add(citation_file) - - git.commit(m=f"Bump to version {next_tag}") + git.commit(m=f"Bump to version {next_tag}", no_verify=True) target_hash = str(git("rev-parse", "HEAD")).strip() print("target_hash:", target_hash) @@ -314,6 +322,33 @@ async def make_release( ) +def execute_bump(next_version: str): + version_file = Path("version_number") + + version_file.write_text(next_version) + git.add(version_file) + + repo = get_repo() + zenodo_file = Path(".zenodo.json") + update_zenodo(zenodo_file, repo, next_version) + git.add(zenodo_file) + + citation_file = Path("CITATION.cff") + update_citation(citation_file, next_version) + git.add(citation_file) + + +@app.command() +def bump( + next_version: str = typer.Argument(..., help="Format: X.Y.Z"), commit: bool = False +): + execute_bump(next_version) + next_tag = f"v{next_version}" + + if commit: + git.commit(m=f"Bump to version {next_tag}", no_verify=True) + + async def get_release_branch_version( repo: str, target_branch: str, gh: GitHubAPI ) -> str: @@ -416,6 +451,7 @@ async def pr_action( bump = evaluate_version_bump(commits) print("bump:", bump) + current_version_obj = Version next_version = get_new_version(current_version, bump) print("next version:", next_version) next_tag = f"v{next_version}" diff --git a/CI/release_requirements.txt b/CI/release_requirements.txt index 1c28cac777f..ea26f526a40 100644 --- a/CI/release_requirements.txt +++ b/CI/release_requirements.txt @@ -1,138 +1,108 @@ # -# This file is autogenerated by pip-compile with python 3.8 -# To update, run: +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: # # pip-compile CI/release_requirements.in # -aiohttp==3.7.4.post0 +aiohttp==3.9.1 # via -r CI/release_requirements.in -async-timeout==3.0.1 +aiosignal==1.3.1 # via aiohttp -attrs==21.2.0 +annotated-types==0.6.0 + # via pydantic +async-timeout==4.0.3 # via aiohttp -bleach==4.1.0 - # via readme-renderer -certifi==2021.5.30 +attrs==23.1.0 + # via aiohttp +certifi==2023.11.17 # via requests -cffi==1.14.6 +cffi==1.16.0 # via cryptography -chardet==4.0.0 - # via aiohttp -charset-normalizer==2.0.4 +charset-normalizer==3.3.2 # via requests -click==8.0.1 +click==8.1.7 # via - # click-log # python-semantic-release # typer -click-log==0.3.2 +cryptography==41.0.7 + # via pyjwt +dotty-dict==1.3.1 # via python-semantic-release -colorama==0.4.4 - # via twine -cryptography==3.4.8 +frozenlist==1.4.0 # via - # pyjwt - # secretstorage -docutils==0.17.1 - # via readme-renderer -dotty-dict==1.3.0 - # via python-semantic-release -gidgethub==5.0.1 + # aiohttp + # aiosignal +gidgethub==5.3.0 # via -r CI/release_requirements.in -gitdb==4.0.7 +gitdb==4.0.11 # via gitpython -gitpython==3.1.18 +gitpython==3.1.40 # via python-semantic-release -idna==3.2 +idna==3.6 # via # requests # yarl -importlib-metadata==4.8.1 - # via - # keyring - # twine -invoke==1.6.0 +importlib-resources==6.1.1 # via python-semantic-release -jeepney==0.7.1 - # via - # keyring - # secretstorage -keyring==23.1.0 - # via twine -multidict==5.1.0 +jinja2==3.1.2 + # via python-semantic-release +markdown-it-py==3.0.0 + # via rich +markupsafe==2.1.3 + # via jinja2 +mdurl==0.1.2 + # via markdown-it-py +multidict==6.0.4 # via # aiohttp # yarl -packaging==21.0 - # via bleach -pkginfo==1.7.1 - # via twine -pycparser==2.20 +pycparser==2.21 # via cffi -pygments==2.10.0 - # via readme-renderer -pyjwt[crypto]==2.1.0 - # via gidgethub -pyparsing==2.4.7 - # via packaging -python-dotenv==0.19.0 +pydantic==2.5.2 + # via python-semantic-release +pydantic-core==2.14.5 + # via pydantic +pygments==2.17.2 + # via rich +pyjwt[crypto]==2.8.0 + # via + # gidgethub + # pyjwt +python-dotenv==1.0.0 # via -r CI/release_requirements.in -python-gitlab==2.10.1 +python-gitlab==3.15.0 # via python-semantic-release -python-semantic-release==7.19.1 +python-semantic-release==8.3.0 # via -r CI/release_requirements.in -pyyaml==5.4.1 +pyyaml==6.0.1 # via -r CI/release_requirements.in -readme-renderer==29.0 - # via twine -requests==2.26.0 +requests==2.31.0 # via # python-gitlab # python-semantic-release # requests-toolbelt - # twine -requests-toolbelt==0.9.1 - # via - # python-gitlab - # twine -rfc3986==1.5.0 - # via twine -secretstorage==3.3.1 - # via keyring -semver==2.13.0 +requests-toolbelt==1.0.0 + # via python-gitlab +rich==13.7.0 # via python-semantic-release -setuptools-scm==6.0.1 - # via dotty-dict -sh==1.14.2 +sh==2.0.6 # via -r CI/release_requirements.in -six==1.16.0 - # via - # bleach - # readme-renderer -smmap==4.0.0 - # via gitdb -tomlkit==0.7.0 +shellingham==1.5.4 # via python-semantic-release -tqdm==4.62.2 - # via twine -twine==3.4.2 +smmap==5.0.1 + # via gitdb +tomlkit==0.12.3 # via python-semantic-release -typer==0.4.0 +typer==0.9.0 # via -r CI/release_requirements.in -typing-extensions==3.10.0.1 - # via aiohttp -uritemplate==3.0.1 +typing-extensions==4.8.0 + # via + # pydantic + # pydantic-core + # typer +uritemplate==4.1.1 # via gidgethub -urllib3==1.26.6 +urllib3==2.1.0 # via requests -webencodings==0.5.1 - # via bleach -wheel==0.37.0 - # via python-semantic-release -yarl==1.6.3 +yarl==1.9.4 # via aiohttp -zipp==3.5.0 - # via importlib-metadata - -# The following packages are considered to be unsafe in a requirements file: -# setuptools