diff --git a/.github/workflows/tag.yml b/.github/workflows/tag.yml deleted file mode 100644 index 5bad5a6c6c2..00000000000 --- a/.github/workflows/tag.yml +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: Sync tags with ansible-core releases - -"on": - workflow_dispatch: - inputs: - dry-run: - type: boolean - default: false - description: "Select to run the tag script in dry-run mode" - schedule: - - cron: "0 * * * *" # Hourly - -jobs: - tag: - runs-on: "ubuntu-latest" - environment: github-bot - permissions: - contents: write - steps: - - name: Generate temp GITHUB_TOKEN - id: create_token - uses: tibdex/github-app-token@v2 - with: - app_id: ${{ secrets.BOT_APP_ID }} - private_key: ${{ secrets.BOT_APP_KEY }} - - name: Check out us - uses: actions/checkout@v4 - with: - path: ansible-documentation - fetch-depth: 0 - token: "${{ steps.create_token.outputs.token }}" - - name: Check out core - uses: actions/checkout@v4 - with: - repository: ansible/ansible - path: ansible - fetch-depth: 0 - - name: Setup nox - uses: wntrblm/nox@2024.04.15 - with: - python-versions: "3.12" - - name: Set up git committer - run: | - ./hacking/get_bot_user.sh "ansible-documentation-bot" "Ansible Documentation Bot" - working-directory: ansible-documentation - - name: Run tag script - run: nox -s tag -- tag ${{ inputs.dry-run && '--no-push' || '' }} - working-directory: ansible-documentation diff --git a/hacking/tagger/tag.py b/hacking/tagger/tag.py deleted file mode 100755 index d2067583160..00000000000 --- a/hacking/tagger/tag.py +++ /dev/null @@ -1,421 +0,0 @@ -#!/usr/bin/env python3 - -# Copyright (C) 2024 Maxwell G -# SPDX-License-Identifier: GPL-3.0-or-later -# GNU General Public License v3.0+ -# (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) - -""" -Script to handle tagging versions in the ansible-documentation repo in sync -with ansible-core. -""" - -from __future__ import annotations - -import datetime -from collections.abc import Iterable -from dataclasses import dataclass -from pathlib import Path -from string import Template -from types import SimpleNamespace -from typing import Any, List, NamedTuple, NoReturn, Optional - -import click -import git -import git.objects.util -import typer - -from packaging.version import Version - -MESSAGE = Template( - """\ -${version_str} - -This tag contains a snapshot of the ansible-documentation ${branch} branch -at the time of the ansible-core ${version_str} release. -""" -) -# hacking/tagger -HERE = Path(__file__).resolve().parent -ROOT = HERE.parent.parent - -DEFAULT_ANSIBLE_CORE_CHECKOUT = ROOT.parent.joinpath("ansible") -DEFAULT_REMOTE = "origin" -DEFAULT_ACTIVE_BRANCHES: tuple[str, ...] = ( - "stable-2.14", - "stable-2.15", - "stable-2.16", - "stable-2.17", -) - - -def get_tags(repo: git.Repo) -> list[str]: - """ - Args: - repo: - A repo object - Returns: - A list of tag names as strings - """ - return [tag.name.removeprefix("refs/tags/") for tag in repo.tags] - - -def filter_tags(tags: Iterable[str], major_minor: str) -> dict[str, Version]: - """ - Args: - tags: - Iterable of tag names as strings - major_minor: - `{version.major}.{version.minor}` of an ansible-core branch - Returns: - Sorted (newest->oldest) dict of tag names that are part of - `major_minor` mapped to parsed `packaging.version.Version`s - """ - tags = { - tag: Version(stripped) - for tag in tags - if (stripped := tag.lstrip("v")).startswith(major_minor) - } - return dict(sorted(tags.items(), reverse=True, key=lambda x: x[1])) - - -def get_tag_datetime(tag: git.TagReference) -> datetime.datetime: - """ - Args: - tag: - Lightweight tag reference - Returns: - A `datetime.datetime` of the tagged date or the committed date for a - non-annotated tag - """ - if tag.tag: - return git.objects.util.from_timestamp( - tag.tag.tagged_date, tag.tag.tagger_tz_offset - ) - return tag.commit.committed_datetime - - -def _get_last_commit_before( - commits: Iterable[git.objects.Commit], before: datetime.datetime -) -> git.objects.Commit: - for commit in commits: - if commit.committed_datetime <= before: - return commit - raise ValueError("No commit found!") - - -def get_last_hash( - docs_repo: git.Repo, core_tag: git.TagReference, branch: str, remote: str -) -> str: - """ - Get the last commit before the datetime of ansible-core's release of TAG. - - Args: - docs_repo: - ansible-documentation `git.Repo` object - core_tag: - `git.TagReference` for the corresponding tag in ansible-core - branch: - Branch name in which to search for the properly timed commit - - Returns: - Commit hash - - Raises: - ValueError: - No commit was found before the datetime of ansible-core's release of TAG - """ - return _get_last_commit_before( - commits=docs_repo.iter_commits(f"{remote}/{branch}", first_parent=True), - before=get_tag_datetime(core_tag), - ) - - -def get_branch(tag_name: str, /) -> str: - """ - Determine a `stable-XX.XX` branch name based on `tag_name` - """ - version = Version(tag_name.lstrip("v")) - major_minor = f"{version.major}.{version.minor}" - return "stable-" + major_minor - - -def v_prefix_tag(name: str, /) -> str: - """ - Ensure a tag/version has a `v` prefix - """ - return "v" + name.lstrip("v") - - -# START: typer CLI code - -app = typer.Typer() - - -def fatal(__msg: object, /, *, returncode: int = 1) -> NoReturn: - typer.secho(f"! {__msg}", err=True, fg="red") - raise typer.Exit(returncode) - - -def msg(__msg: object, not_on_quiet: bool = True, /, **kwargs: Any) -> None: - if not_on_quiet: - try: - quiet = click.get_current_context().ensure_object(Args).quiet - except Exception: - quiet = False - if quiet: - return - kwarg: dict[str, Any] = {"err": True, "fg": "blue"} | kwargs - typer.secho(f"* {__msg}", **kwarg) - - -@dataclass(kw_only=True) -class Args: - """ - Context for global arguments - """ - - docs_repo_path: Path - docs_repo: git.Repo - docs_remote: str - core_repo_path: Path - core_repo: git.Repo - core_remote: str - quiet: bool - - -def ensure_tag(tag: git.TagReference) -> None: - """ - Ensure a `git.TagReference` actually object - """ - try: - _ = tag.object - except ValueError: - name = tag.name.removeprefix("refs/tags/") - fatal(f"Tag {name} does not exist in core!") - - -def get_new_tags(args: Args, branch: str) -> dict[str, Version]: - """ - Returns: - Sorted (newest->oldest) dict of new tag names mapped to parsed - `packaging.version.Version`s - """ - core_tags, our_tags = get_tags(args.core_repo), get_tags(args.docs_repo) - core_filtered_tags = filter_tags(core_tags, branch.removeprefix("stable-")) - our_filtered_tags = filter_tags(our_tags, branch.removeprefix("stable-")) - missing_tags: dict[str, Version] = {} - for tag, version in core_filtered_tags.items(): - if tag in our_filtered_tags: - break - missing_tags[tag] = version - return missing_tags - - -class BranchTagRef(NamedTuple): - branch: str - tag: str - ref: str - - -def branch_tag_ref( - args: Args, branch: str | None, tag: str, ref: str | None -) -> BranchTagRef: - tag = v_prefix_tag(tag) - branch = branch or get_branch(tag) - core_tag = args.core_repo.tag(tag) - ensure_tag(core_tag) - if not ref: - ref = get_last_hash(args.docs_repo, core_tag, branch, args.docs_remote) - return BranchTagRef(branch, tag, ref) - - -def create_tag( - args: Args, branch: str, tag: str, ref: str, *, push: bool -) -> git.TagReference: - """ - Create and push a tag with the proper message - - Args: - args: - CLI context `Args` object - branch: - Branch name - tag: - Tag name - ref: - Reference to tag - """ - message = MESSAGE.substitute(version_str=tag.lstrip("v"), branch=branch) - msg(f"Tagging {ref} as {tag}") - tag_ref = git.TagReference.create(args.docs_repo, tag, ref, message) - if push: - print(f"Pushing {tag} to {args.docs_remote}") - args.docs_repo.remote(args.docs_remote).push(tag) - return tag_ref - - -PARAMS = SimpleNamespace( - branches=typer.Option( - None, - "-b", - "--branch", - help="Branches in which to search for tags." - " Can be specified multiple times." - f" Defaults to {DEFAULT_ACTIVE_BRANCHES}", - ), - branch=typer.Option( - None, - "-b", - "--branch", - help="Branch name. Autodetect based on --tag by deafult.", - ), - tag_required=typer.Option( - ..., - "-t", - "--tag", - help="Tag name", - ), - ref=typer.Option( - ..., - "-r", - "--ref", - help="Tag reference", - ), -) - - -@app.callback(help=__doc__) -def callback( - ctx: typer.Context, - docs_repo_path: Path = typer.Option( - ROOT, - "--docs", - help="Path to ansible-documentation checkout", - dir_okay=True, - file_okay=False, - exists=True, - ), - core_repo_path: Path = typer.Option( - DEFAULT_ANSIBLE_CORE_CHECKOUT, - "--core", - help="Path to core checkout", - dir_okay=True, - file_okay=False, - exists=True, - ), - remote: Optional[str] = typer.Option( - None, - help="Git Remote name for ansible-core and ansible-documentation checkouts." - f" Default: {DEFAULT_REMOTE}", - ), - core_remote: Optional[str] = typer.Option( - None, help="Override remote name for core checkout" - ), - docs_remote: Optional[str] = typer.Option( - None, help="Override remote name for docs checkout" - ), - fetch: bool = typer.Option(True, help="Whether to fetch repos"), - quiet: bool = typer.Option(False, help="Silence logging"), -): - """ - Process global CLI arguments and create a context object to store them - """ - core_remote = core_remote or remote or DEFAULT_REMOTE - docs_remote = docs_remote or remote or DEFAULT_REMOTE - docs_repo = git.Repo(docs_repo_path) - core_repo = git.Repo(core_repo_path) - args = Args( - docs_repo_path=docs_repo_path, - docs_repo=docs_repo, - docs_remote=docs_remote, - core_repo_path=core_repo_path, - core_repo=core_repo, - core_remote=core_remote, - quiet=quiet, - ) - ctx.obj = args - if fetch: - fetch_all(args) - - -def fetch_all(args: Args) -> None: - remotes = { - "docs": (args.docs_repo, args.docs_remote), - "core": (args.core_repo, args.core_remote), - } - for name, (repo, cur_remote) in remotes.items(): - msg(f"Fetching {cur_remote} from {name} repo...") - repo.remote(cur_remote).fetch() - - -@app.command(name="new-tags") -def new_tags_command( - ctx: typer.Context, branches: Optional[List[str]] = PARAMS.branches -) -> None: - """ - List new tags in ansible-core that are not tagged here - """ - args = ctx.ensure_object(Args) - branches = branches or list(DEFAULT_ACTIVE_BRANCHES) - missing_tags = [tag for branch in branches for tag in get_new_tags(args, branch)] - if missing_tags: - print("\n".join(missing_tags)) - ctx.exit(0 if missing_tags else 1) - - -@app.command(name="hash") -def hash_command( - ctx: typer.Context, - tag: str = PARAMS.tag_required, - branch: Optional[str] = PARAMS.branch, -) -> None: - """ - Get the last commit hash before the datetime of ansible-core's release of TAG. - """ - args = ctx.ensure_object(Args) - _, _, ref = branch_tag_ref(args, branch, tag, None) - print(ref) - - -@app.command(name="mantag") -def mantag_command( - ctx: typer.Context, - tag: str = PARAMS.tag_required, - ref: str = PARAMS.ref, - branch: Optional[str] = PARAMS.branch, - push: bool = True, -) -> None: - """ - Manually tag a release - """ - args = ctx.ensure_object(Args) - triplet = branch_tag_ref(args, branch, tag, ref) - create_tag(args, *triplet, push=push) - - -@app.command(name="tag") -def tag_command( - ctx: typer.Context, - branches: Optional[List[str]] = PARAMS.branches, - push: bool = True, -): - """ - Determine the missing ansible-core releases from `--branch`, create - corresponding tags for each release in the ansible-documentation repo, and - push them. - """ - args = ctx.ensure_object(Args) - branches = branches or list(DEFAULT_ACTIVE_BRANCHES) - triplets: list[BranchTagRef] = [ - branch_tag_ref(args, branch, tag, None) - for branch in branches - for tag in get_new_tags(args, branch) - ] - - for triplet in triplets: - create_tag(args, *triplet, push=push) - - -if __name__ == "__main__": - app() diff --git a/noxfile.py b/noxfile.py index 395d3aaf101..d550a26f2f7 100644 --- a/noxfile.py +++ b/noxfile.py @@ -12,7 +12,6 @@ LINT_FILES: tuple[str, ...] = ( "hacking/pr_labeler/pr_labeler", - "hacking/tagger/tag.py", "noxfile.py", *iglob("docs/bin/*.py"), ) diff --git a/tests/tag.in b/tests/tag.in deleted file mode 100644 index 220933af86f..00000000000 --- a/tests/tag.in +++ /dev/null @@ -1,3 +0,0 @@ -gitpython -packaging -typer diff --git a/tests/tag.txt b/tests/tag.txt deleted file mode 100644 index 177f3250cb7..00000000000 --- a/tests/tag.txt +++ /dev/null @@ -1,30 +0,0 @@ -# -# This file is autogenerated by pip-compile with Python 3.11 -# by the following command: -# -# pip-compile --allow-unsafe --output-file=tests/tag.txt --strip-extras tests/tag.in -# -click==8.1.7 - # via typer -gitdb==4.0.11 - # via gitpython -gitpython==3.1.43 - # via -r tests/tag.in -markdown-it-py==3.0.0 - # via rich -mdurl==0.1.2 - # via markdown-it-py -packaging==24.1 - # via -r tests/tag.in -pygments==2.18.0 - # via rich -rich==13.8.1 - # via typer -shellingham==1.5.4 - # via typer -smmap==5.0.1 - # via gitdb -typer==0.12.5 - # via -r tests/tag.in -typing-extensions==4.12.2 - # via typer diff --git a/tests/typing.in b/tests/typing.in index 9701e2c5780..63112a0d90a 100644 --- a/tests/typing.in +++ b/tests/typing.in @@ -1,4 +1,3 @@ -r ../hacking/pr_labeler/requirements.txt --r tag.in mypy nox diff --git a/tests/typing.txt b/tests/typing.txt index 421cca04549..366fd88edf6 100644 --- a/tests/typing.txt +++ b/tests/typing.txt @@ -15,9 +15,7 @@ cffi==1.17.1 charset-normalizer==3.3.2 # via requests click==8.1.7 - # via - # typer - # typer-slim + # via typer-slim codeowners==0.7.0 # via -r tests/../hacking/pr_labeler/requirements.txt colorlog==6.8.2 @@ -30,20 +28,12 @@ distlib==0.3.8 # via virtualenv filelock==3.16.0 # via virtualenv -gitdb==4.0.11 - # via gitpython -gitpython==3.1.43 - # via -r tests/tag.in idna==3.9 # via requests jinja2==3.1.4 # via -r tests/../hacking/pr_labeler/requirements.txt -markdown-it-py==3.0.0 - # via rich markupsafe==2.1.5 # via jinja2 -mdurl==0.1.2 - # via markdown-it-py mypy==1.11.2 # via -r tests/typing.in mypy-extensions==1.0.0 @@ -51,31 +41,19 @@ mypy-extensions==1.0.0 nox==2024.4.15 # via -r tests/typing.in packaging==24.1 - # via - # -r tests/tag.in - # nox + # via nox platformdirs==4.3.3 # via virtualenv pycparser==2.22 # via cffi pygithub==2.4.0 # via -r tests/../hacking/pr_labeler/requirements.txt -pygments==2.18.0 - # via rich pyjwt==2.9.0 # via pygithub pynacl==1.5.0 # via pygithub requests==2.32.3 # via pygithub -rich==13.8.1 - # via typer -shellingham==1.5.4 - # via typer -smmap==5.0.1 - # via gitdb -typer==0.12.5 - # via -r tests/tag.in typer-slim==0.12.5 # via -r tests/../hacking/pr_labeler/requirements.txt typing-extensions==4.12.2 @@ -83,7 +61,6 @@ typing-extensions==4.12.2 # codeowners # mypy # pygithub - # typer # typer-slim urllib3==2.2.3 # via