diff --git a/.ci/scripts/skip_tests.py b/.ci/scripts/skip_tests.py new file mode 100755 index 0000000..2a21a72 --- /dev/null +++ b/.ci/scripts/skip_tests.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +""" +skip_tests.py - Check if only documentation files were changed in a git branch + +Usage: + ./skip_tests.py + +Arguments: + git_root: The root directory of the git project + reference_branch: The branch to compare against + +Returns: + 0: Skip + 1: NoSkip + *: Error +""" + +import sys +import os +import re +import git +import textwrap +import argparse + +DOC_PATTERNS = [ + r"^docs/", + r"\.md$", + r"\.txt$", + r"LICENSE.*", + r"CHANGELOG.*", + r"CHANGES.*", + r"CONTRIBUTING.*", +] + +# Exit codes +CODE_SKIP = 0 +CODE_NO_SKIP = 1 +CODE_ERROR = 2 + + +def main() -> int: + git_root, reference_branch = get_args() + changed_files = get_changed_files(git_root, reference_branch) + if not changed_files: + return CODE_SKIP + doc_files = [f for f in changed_files if is_doc_file(f)] + not_doc_files = set(changed_files) - set(doc_files) + print_changes(doc_files, not_doc_files) + if not_doc_files: + return CODE_NO_SKIP + else: + return CODE_SKIP + + +# Utils + + +def get_changed_files(git_root: str, reference_branch: str) -> list[str]: + """Get list of files changed between current branch and reference branch.""" + repo = git.Repo(git_root) + diff_index = repo.git.diff("--name-only", reference_branch).strip() + if not diff_index: + return [] + return [f.strip() for f in diff_index.split("\n") if f.strip()] + + +def is_doc_file(file_path: str) -> bool: + """Check if a file is a documentation file.""" + for pattern in DOC_PATTERNS: + if re.search(pattern, file_path): + return True + return False + + +def print_changes(doc_files: list[str], not_doc_files: list[str]) -> None: + display_doc = " \n".join(doc_files) + print(f"doc_files({len(doc_files)})") + if doc_files: + display_doc = "\n".join(doc_files) + print(textwrap.indent(display_doc, " ")) + + print(f"non_doc_files({len(not_doc_files)})") + if not_doc_files: + display_non_doc = " \n".join(not_doc_files) + print(textwrap.indent(display_non_doc, " ")) + + +def get_args() -> tuple[str, str]: + """Parse command line arguments and validate them.""" + parser = argparse.ArgumentParser(description="Check if CI can skip tests for a git branch") + parser.add_argument("git_root", help="The root directory of the git project") + parser.add_argument("reference_branch", help="The branch to compare against") + args = parser.parse_args() + git_root = os.path.abspath(args.git_root) + ref_branch = args.reference_branch + + if not os.path.exists(git_root): + raise ValueError(f"Git root directory does not exist: {git_root}") + if not os.path.isdir(git_root): + raise ValueError(f"Git root is not a directory: {git_root}") + try: + git.Repo(git_root) + except git.InvalidGitRepositoryError: + raise ValueError(f"Directory is not a git repository: {git_root}") + return git_root, ref_branch + + +if __name__ == "__main__": + try: + sys.exit(main()) + except Exception as e: + print(e) + sys.exit(CODE_ERROR) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d059eba..2c763b8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,10 +43,49 @@ jobs: run: | .github/workflows/scripts/check_commit.sh + check-changes: + runs-on: ubuntu-latest + outputs: + run_tests: ${{ steps.check.outputs.run_tests }} + steps: + - uses: "actions/checkout@v4" + with: + fetch-depth: 0 + path: "pulp_npm" + + - uses: "actions/setup-python@v5" + with: + python-version: "3.12" + + - name: "Install python dependencies" + run: | + echo ::group::PYDEPS + pip install gitpython + echo ::endgroup:: + + - name: Analyze changed files + shell: bash + id: check + run: | + set +e + BASE_REF=${{ github.event.pull_request.base.sha }} + echo "Checking against:" + git name-rev $BASE_REF + python3 .ci/scripts/skip_tests.py . $BASE_REF + exit_code=$? + if [ $exit_code -ne 0 ] && [ $exit_code -ne 1 ]; then + echo "Error: skip_tests.py returned unexpected exit code $exit_code" + exit $exit_code + fi + echo "run_tests=$exit_code" >> $GITHUB_OUTPUT + docs: uses: "./.github/workflows/docs.yml" lint: + needs: + - "check-changes" + if: needs.check-changes.outputs.run_tests == '1' uses: "./.github/workflows/lint.yml" build: @@ -84,6 +123,7 @@ jobs: # This is a dummy dependent task to have a single entry for the branch protection rules. runs-on: "ubuntu-latest" needs: + - "check-changes" - "check-commits" - "lint" - "test" @@ -93,6 +133,13 @@ jobs: - name: "Collect needed jobs results" working-directory: "." run: | - echo '${{toJson(needs)}}' | jq -r 'to_entries[]|select(.value.result!="success")|.key + ": " + .value.result' - echo '${{toJson(needs)}}' | jq -e 'to_entries|map(select(.value.result!="success"))|length == 0' + if [ ${{ needs.check-changes.outputs.run_tests }} == "1" ]; then + # Full test run - check all jobs + echo '${{toJson(needs)}}' | jq -r 'to_entries[]|select(.value.result!="success")|.key + ": " + .value.result' + echo '${{toJson(needs)}}' | jq -e 'to_entries|map(select(.value.result!="success"))|length == 0' + else + # Docs-only run - check only required jobs (exclude lint and test) + echo '${{toJson(needs)}}' | jq -r 'to_entries[]|select(.key != "lint" and .key != "test")|select(.value.result!="success")|.key + ": " + .value.result' + echo '${{toJson(needs)}}' | jq -e 'to_entries|map(select(.key != "lint" and .key != "test"))|map(select(.value.result!="success"))|length == 0' + fi echo "CI says: Looks good!" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d38ded0..976a372 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,10 +32,12 @@ jobs: with: python-version: "3.11" + # Click is pinned because of: + # https://github.com/pallets/click/issues/3065 - name: "Install python dependencies" run: | echo ::group::PYDEPS - pip install bump-my-version towncrier + pip install bump-my-version towncrier 'click<8.3' echo ::endgroup:: - name: "Configure Git with pulpbot name and email" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index feae40c..8c2ec27 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -71,7 +71,7 @@ jobs: - name: "Install python dependencies" run: | echo ::group::PYDEPS - pip install towncrier twine wheel httpie docker netaddr boto3 'ansible~=10.3.0' mkdocs jq jsonpatch bump-my-version + pip install towncrier twine wheel httpie docker netaddr boto3 'ansible~=10.3.0' mkdocs jq jsonpatch bump-my-version 'click<8.3' echo "HTTPIE_CONFIG_DIR=$GITHUB_WORKSPACE/pulp_npm/.ci/assets/httpie/" >> $GITHUB_ENV echo ::endgroup:: diff --git a/lint_requirements.txt b/lint_requirements.txt index 458ba9b..cd76d27 100644 --- a/lint_requirements.txt +++ b/lint_requirements.txt @@ -6,6 +6,9 @@ # For more info visit https://github.com/pulp/plugin_template black==24.3.0 +# Click is pinned because of: +# https://github.com/pallets/click/issues/3065 +click<8.3 bump-my-version check-manifest flake8