diff --git a/.ci/bootstrap_catdog.sh b/.ci/bootstrap_catdog.sh index 21779f76..88d651c3 100755 --- a/.ci/bootstrap_catdog.sh +++ b/.ci/bootstrap_catdog.sh @@ -7,7 +7,7 @@ if [ "$GITHUB_EVENT_NAME" = "pull_request" ] then COMMIT_MSG=$(git log --format=%B -n 1 HEAD^2) else - COMMIT_MSG="Initial commit\n\n[noissue]" + COMMIT_MSG="Initial commit" fi echo "${COMMIT_MSG}" diff --git a/.ci/scripts/pr_labels.py b/.ci/scripts/pr_labels.py new file mode 100755 index 00000000..9d637b6b --- /dev/null +++ b/.ci/scripts/pr_labels.py @@ -0,0 +1,57 @@ +#!/bin/env python3 + +# This script is running with elevated privileges from the main branch against pull requests. + +import re +import sys +import tomllib +from pathlib import Path + +from git import Repo + + +def main(): + assert len(sys.argv) == 3 + + with open("pyproject.toml", "rb") as fp: + PYPROJECT_TOML = tomllib.load(fp) + BLOCKING_REGEX = re.compile(r"DRAFT|WIP|NO\s*MERGE|DO\s*NOT\s*MERGE|EXPERIMENT") + ISSUE_REGEX = re.compile(r"(?:fixes|closes)[\s:]+#(\d+)") + CHERRY_PICK_REGEX = re.compile(r"^\s*\(cherry picked from commit [0-9a-f]*\)\s*$") + CHANGELOG_EXTS = [ + f".{item['directory']}" for item in PYPROJECT_TOML["tool"]["towncrier"]["type"] + ] + + repo = Repo(".") + + base_commit = repo.commit(sys.argv[1]) + head_commit = repo.commit(sys.argv[2]) + + pr_commits = list(repo.iter_commits(f"{base_commit}..{head_commit}")) + + labels = { + "multi-commit": len(pr_commits) > 1, + "cherry-pick": False, + "no-issue": False, + "no-changelog": False, + "wip": False, + } + for commit in pr_commits: + labels["wip"] |= BLOCKING_REGEX.search(commit.summary) is not None + no_issue = ISSUE_REGEX.search(commit.message, re.IGNORECASE) is None + labels["no-issue"] |= no_issue + cherry_pick = CHERRY_PICK_REGEX.search(commit.message) is not None + labels["cherry-pick"] |= cherry_pick + changelog_snippets = [ + k + for k in commit.stats.files + if k.startswith("CHANGES/") and Path(k).suffix in CHANGELOG_EXTS + ] + labels["no-changelog"] |= not changelog_snippets + + print("ADD_LABELS=" + ",".join((k for k, v in labels.items() if v))) + print("REMOVE_LABELS=" + ",".join((k for k, v in labels.items() if not v))) + + +if __name__ == "__main__": + main() diff --git a/.github/workflows/pr_checks.yml b/.github/workflows/pr_checks.yml index 20eb115e..61a9c077 100644 --- a/.github/workflows/pr_checks.yml +++ b/.github/workflows/pr_checks.yml @@ -1,62 +1,67 @@ # WARNING: DO NOT EDIT! # # This file was generated by plugin_template, and is managed by it. Please use -# './plugin-template --github pulp_catdog' to update this file. +# './plugin-template --github pulpcore' to update this file. # # For more info visit https://github.com/pulp/plugin_template --- -name: Plugin template PR static checks +name: "Core PR static checks" on: pull_request_target: - types: [opened, synchronize, reopened] + types: ["opened", "synchronize", "reopened"] # This workflow runs with elevated permissions. # Do not even think about running a single bit of code from the PR. # Static analysis should be fine however. concurrency: - group: ${{ github.event.pull_request.number }}-${{ github.workflow }} + group: "${{ github.event.pull_request.number }}-${{ github.workflow }}" cancel-in-progress: true jobs: - single_commit: - runs-on: ubuntu-latest - name: Label multiple commit PR + apply_labels: + runs-on: "ubuntu-latest" + name: "Label PR" permissions: - pull-requests: write + pull-requests: "write" steps: - uses: "actions/checkout@v4" with: fetch-depth: 0 - - name: Commit Count Check + - uses: "actions/setup-python@v5" + with: + python-version: "3.11" + - name: "Determine PR labels" run: | + pip install GitPython==3.1.42 git fetch origin ${{ github.event.pull_request.head.sha }} - echo "COMMIT_COUNT=$(git log --oneline --no-merges origin/${{ github.base_ref }}..${{ github.event.pull_request.head.sha }} | wc -l)" >> "$GITHUB_ENV" - - uses: actions/github-script@v7 + python .ci/scripts/pr_labels.py "origin/${{ github.base_ref }}" "${{ github.event.pull_request.head.sha }}" >> "$GITHUB_ENV" + - uses: "actions/github-script@v7" + name: "Apply PR Labels" with: script: | - const labelName = "multi-commit"; - const { COMMIT_COUNT } = process.env; + const { ADD_LABELS, REMOVE_LABELS } = process.env; - if (COMMIT_COUNT == 1) - { - try { - await github.rest.issues.removeLabel({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - name: labelName, - }); - } catch(err) { + if (REMOVE_LABELS.length) { + for await (const labelName of REMOVE_LABELS.split(",")) { + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: labelName, + }); + } catch(err) { + } } } - else - { + if (ADD_LABELS.length) { await github.rest.issues.addLabels({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - labels: [labelName], + labels: ADD_LABELS.split(","), }); } +... diff --git a/README.md b/README.md index 5dd2155d..3e5bad37 100644 --- a/README.md +++ b/README.md @@ -133,8 +133,6 @@ The following settings are stored in `template_config.yml`. the CI will run an additional pytest call running pulpcore tests with that mark. - noissue_marker A string that is used to mark a commit as not attached to an issue. - stalebot A boolean that indicates whether to use stalebot or not. stalebot_days_until_stale diff --git a/plugin-template b/plugin-template index ea33caf3..da4342fd 100755 --- a/plugin-template +++ b/plugin-template @@ -40,7 +40,6 @@ DEFAULT_SETTINGS = { "github_org": "pulp", "latest_release_branch": None, "lint_requirements": True, - "noissue_marker": "[noissue]", "os_required_packages": [], "parallel_test_workers": 8, "plugin_app_label": None, diff --git a/scripts/update_ci.sh b/scripts/update_ci.sh index d9ae7714..3926f86c 100755 --- a/scripts/update_ci.sh +++ b/scripts/update_ci.sh @@ -20,13 +20,8 @@ fi plugin_name="$(python ../plugin_template/scripts/get_template_config_value.py plugin_name)" ci_update_docs="$(python ../plugin_template/scripts/get_template_config_value.py ci_update_docs)" -noissue_marker="$(python ../plugin_template/scripts/get_template_config_value.py noissue_marker)" use_black="$(python ../plugin_template/scripts/get_template_config_value.py black)" -if [[ -z "${noissue_marker}" ]]; then - noissue_marker="[noissue]" -fi - if [[ "${ci_update_docs}" == "True" ]]; then docs=("--docs") else @@ -46,7 +41,7 @@ fi if [[ $(git status --porcelain) ]]; then git add -A - git commit -m "Update CI files" -m "${noissue_marker}" + git commit -m "Update CI files" else echo "No updates needed" fi @@ -59,7 +54,7 @@ then if [[ "$(git status --porcelain)" ]] then git add -A - git commit -m "Reformat with black" -m "${noissue_marker}" + git commit -m "Reformat with black" else echo "No formatting change needed" fi @@ -70,6 +65,6 @@ if [[ "$plugin_name" != "pulpcore" ]]; then python ../plugin_template/scripts/update_core_lowerbound.py if [[ $(git status --porcelain) ]]; then git add -A - git commit -m "Bump pulpcore lowerbounds to supported branch" -m "$noissue_marker" + git commit -m "Bump pulpcore lowerbounds to supported branch" fi fi diff --git a/templates/github/.ci/scripts/collect_changes.py.j2 b/templates/github/.ci/scripts/collect_changes.py.j2 index 3983565c..fa9d46af 100755 --- a/templates/github/.ci/scripts/collect_changes.py.j2 +++ b/templates/github/.ci/scripts/collect_changes.py.j2 @@ -98,7 +98,7 @@ def main(): for change in main_changes: fp.write(change[1]) - repo.git.commit("-m", "Update Changelog", "-m" "{{ noissue_marker | default("[noissue]") }}", CHANGELOG_FILE) + repo.git.commit("-m", "Update Changelog", CHANGELOG_FILE) if __name__ == "__main__": diff --git a/templates/github/.ci/scripts/pr_labels.py b/templates/github/.ci/scripts/pr_labels.py new file mode 100755 index 00000000..9d637b6b --- /dev/null +++ b/templates/github/.ci/scripts/pr_labels.py @@ -0,0 +1,57 @@ +#!/bin/env python3 + +# This script is running with elevated privileges from the main branch against pull requests. + +import re +import sys +import tomllib +from pathlib import Path + +from git import Repo + + +def main(): + assert len(sys.argv) == 3 + + with open("pyproject.toml", "rb") as fp: + PYPROJECT_TOML = tomllib.load(fp) + BLOCKING_REGEX = re.compile(r"DRAFT|WIP|NO\s*MERGE|DO\s*NOT\s*MERGE|EXPERIMENT") + ISSUE_REGEX = re.compile(r"(?:fixes|closes)[\s:]+#(\d+)") + CHERRY_PICK_REGEX = re.compile(r"^\s*\(cherry picked from commit [0-9a-f]*\)\s*$") + CHANGELOG_EXTS = [ + f".{item['directory']}" for item in PYPROJECT_TOML["tool"]["towncrier"]["type"] + ] + + repo = Repo(".") + + base_commit = repo.commit(sys.argv[1]) + head_commit = repo.commit(sys.argv[2]) + + pr_commits = list(repo.iter_commits(f"{base_commit}..{head_commit}")) + + labels = { + "multi-commit": len(pr_commits) > 1, + "cherry-pick": False, + "no-issue": False, + "no-changelog": False, + "wip": False, + } + for commit in pr_commits: + labels["wip"] |= BLOCKING_REGEX.search(commit.summary) is not None + no_issue = ISSUE_REGEX.search(commit.message, re.IGNORECASE) is None + labels["no-issue"] |= no_issue + cherry_pick = CHERRY_PICK_REGEX.search(commit.message) is not None + labels["cherry-pick"] |= cherry_pick + changelog_snippets = [ + k + for k in commit.stats.files + if k.startswith("CHANGES/") and Path(k).suffix in CHANGELOG_EXTS + ] + labels["no-changelog"] |= not changelog_snippets + + print("ADD_LABELS=" + ",".join((k for k, v in labels.items() if v))) + print("REMOVE_LABELS=" + ",".join((k for k, v in labels.items() if not v))) + + +if __name__ == "__main__": + main() diff --git a/templates/github/.ci/scripts/validate_commit_message.py.j2 b/templates/github/.ci/scripts/validate_commit_message.py.j2 index 34936588..38e92e37 100644 --- a/templates/github/.ci/scripts/validate_commit_message.py.j2 +++ b/templates/github/.ci/scripts/validate_commit_message.py.j2 @@ -8,7 +8,6 @@ import os import warnings from github import Github -NO_ISSUE = "[noissue]" CHANGELOG_EXTS = [".feature", ".bugfix", ".doc", ".removal", ".misc", ".deprecation"] KEYWORDS = ["fixes", "closes"] @@ -55,15 +54,5 @@ if issues: for issue in pattern.findall(message): __check_status(issue) __check_changelog(issue) -else: - if NO_ISSUE in message: - print("Commit {sha} has no issues but is tagged {tag}.".format(sha=sha[0:7], tag=NO_ISSUE)) - elif "Merge" in message and "cherry picked from commit" in message: - pass - else: - sys.exit( - "Error: no attached issues found for {sha}. If this was intentional, add " - " '{tag}' to the commit message.".format(sha=sha[0:7], tag=NO_ISSUE) - ) print("Commit message for {sha} passed.".format(sha=sha[0:7])) diff --git a/templates/github/.github/workflows/create-branch.yml.j2 b/templates/github/.github/workflows/create-branch.yml.j2 index 80928cbf..d9019def 100644 --- a/templates/github/.github/workflows/create-branch.yml.j2 +++ b/templates/github/.github/workflows/create-branch.yml.j2 @@ -75,10 +75,8 @@ jobs: branch: minor-version-bump base: {{ plugin_default_branch }} title: Bump minor version - body: '[noissue]' commit-message: | Bump minor version - [noissue] delete-branch: true - name: Push release branch diff --git a/templates/github/.github/workflows/pr_checks.yml.j2 b/templates/github/.github/workflows/pr_checks.yml.j2 index fdd84484..71671010 100644 --- a/templates/github/.github/workflows/pr_checks.yml.j2 +++ b/templates/github/.github/workflows/pr_checks.yml.j2 @@ -1,57 +1,63 @@ {% include 'header.j2' %} {% from 'macros.j2' import checkout, + setup_python, with context %} --- -name: {{ plugin_app_label | camel }} PR static checks +name: "{{ plugin_app_label | camel }} PR static checks" on: pull_request_target: - types: [opened, synchronize, reopened] + types: ["opened", "synchronize", "reopened"] # This workflow runs with elevated permissions. # Do not even think about running a single bit of code from the PR. # Static analysis should be fine however. concurrency: - group: {{ '${{ github.event.pull_request.number }}-${{ github.workflow }}' }} + group: "{{ '${{ github.event.pull_request.number }}-${{ github.workflow }}' }}" cancel-in-progress: true jobs: - single_commit: - runs-on: ubuntu-latest - name: Label multiple commit PR + apply_labels: + runs-on: "ubuntu-latest" + name: "Label PR" permissions: - pull-requests: write + pull-requests: "write" steps: - {{ checkout(0) | indent(6) }} - - name: Commit Count Check + {{ checkout(depth=0) | indent(6) }} + {{ setup_python() | indent(6) }} + - name: "Determine PR labels" run: | - git fetch origin {{ '${{ github.event.pull_request.head.sha }}' }} - {{ 'echo "COMMIT_COUNT=$(git log --oneline --no-merges origin/${{ github.base_ref }}..${{ github.event.pull_request.head.sha }} | wc -l)" >> "$GITHUB_ENV"' }} - - uses: actions/github-script@v7 + {%- raw %} + pip install GitPython==3.1.42 + git fetch origin ${{ github.event.pull_request.head.sha }} + python .ci/scripts/pr_labels.py "origin/${{ github.base_ref }}" "${{ github.event.pull_request.head.sha }}" >> "$GITHUB_ENV" + {%- endraw %} + - uses: "actions/github-script@v7" + name: "Apply PR Labels" with: script: | - const labelName = "multi-commit"; - const { COMMIT_COUNT } = process.env; + const { ADD_LABELS, REMOVE_LABELS } = process.env; - if (COMMIT_COUNT == 1) - { - try { - await github.rest.issues.removeLabel({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - name: labelName, - }); - } catch(err) { + if (REMOVE_LABELS.length) { + for await (const labelName of REMOVE_LABELS.split(",")) { + try { + await github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: labelName, + }); + } catch(err) { + } } } - else - { + if (ADD_LABELS.length) { await github.rest.issues.addLabels({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - labels: [labelName], + labels: ADD_LABELS.split(","), }); } +... diff --git a/templates/github/.github/workflows/update_ci.yml.j2 b/templates/github/.github/workflows/update_ci.yml.j2 index 400e97ae..2cd84c2a 100644 --- a/templates/github/.github/workflows/update_ci.yml.j2 +++ b/templates/github/.github/workflows/update_ci.yml.j2 @@ -49,12 +49,8 @@ jobs: committer: "{{ release_user }} <{{ release_email }}>" author: "{{ release_user }} <{{ release_email }}>" title: "Update CI files for branch {{ branch }}" - body: "" branch: "update-ci/{{ branch }}" base: "{{ branch }}" - commit-message: | - Update CI files - - {{ noissue_marker }} delete-branch: true {%- endfor %} +...