From 653265d661f4080964760963b3507885d63a6fdd Mon Sep 17 00:00:00 2001
From: "Felt, Nicholas" <nicholas.felt@tektronix.com>
Date: Fri, 6 Sep 2024 16:18:10 -0700
Subject: [PATCH] feat: Add an additional check for merged PRs to the action
 that finds unreleased changelog items.

Also, Update the cron for releasing a package to once per month, instead of once per week.
---
 .github/renovate.json                         |  5 ++--
 .github/workflows/package-release.yml         |  2 +-
 .github/workflows/test-actions.yml            |  2 ++
 CHANGELOG.md                                  |  1 +
 .../find_unreleased_changelog_items/main.py   | 20 +++++++++------
 .../find_unreleased_changelog_items/readme.md |  9 +++++--
 tests/test_find_unreleased_changelog_items.py | 25 +++++++++++++++++++
 7 files changed, 51 insertions(+), 13 deletions(-)

diff --git a/.github/renovate.json b/.github/renovate.json
index f78f8302..3ed3ef3a 100644
--- a/.github/renovate.json
+++ b/.github/renovate.json
@@ -12,10 +12,11 @@
     "packageRules": [
         {
             "additionalBranchPrefix": "{{#if (equals manager 'github-actions')}}gh-actions{{else}}{{categories}}{{/if}}-deps/",
-            "description": "Set the branch prefix for all updates",
+            "description": "Set the branch prefix and minimum release age for all updates",
             "matchPackageNames": [
                 "*"
-            ]
+            ],
+            "minimumReleaseAge": "5 days"
         },
         {
             "addLabels": [
diff --git a/.github/workflows/package-release.yml b/.github/workflows/package-release.yml
index 881da477..913e88a9 100644
--- a/.github/workflows/package-release.yml
+++ b/.github/workflows/package-release.yml
@@ -13,7 +13,7 @@ on:
           major for non-backward compatible changes.
         options: [patch, minor, major]
   schedule:
-    - cron: 0 16 * * 2
+    - cron: 0 16 1-7 * 2  # Run at 16:00 UTC on the first Tuesday of each month
 concurrency:
   group: pypi
 jobs:
diff --git a/.github/workflows/test-actions.yml b/.github/workflows/test-actions.yml
index de8a77b2..f809d3df 100644
--- a/.github/workflows/test-actions.yml
+++ b/.github/workflows/test-actions.yml
@@ -30,6 +30,8 @@ jobs:
           fi
   test-find_unreleased_changelog_items:
     runs-on: ubuntu-latest
+    env:
+      UNIT_TESTING_FIND_UNRELEASED_CHANGELOG_ITEMS_ACTION: true
     steps:
       - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332  # v4.1.7
       - name: Overwrite CHANGELOG.md with dummy data
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3d15e951..4f465ab3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -28,6 +28,7 @@ Things to be included in the next release go here.
 - Bumped dependency versions.
 - Changed the `_reusable-update-python-and-pre-commit-dependencies.yml` workflow to no longer only work on PRs from Dependabot, users will now need to apply any conditional login in the calling workflow.
 - Updated the `_reusable-update-python-and-pre-commit-dependencies.yml` workflow to allow using [`renovate`](https://docs.renovatebot.com/) instead of Dependabot to update dependencies.
+- Updated the `find_unreleased_changelog_items` action to check for merged PRs since the last release and fail if none are found.
 
 ---
 
diff --git a/actions/find_unreleased_changelog_items/main.py b/actions/find_unreleased_changelog_items/main.py
index 9d48c27c..e231d2f6 100644
--- a/actions/find_unreleased_changelog_items/main.py
+++ b/actions/find_unreleased_changelog_items/main.py
@@ -77,6 +77,7 @@ def main() -> None:
     # Set the filepaths for the template files
     template_changelog_filepath = pathlib.Path(filepath_for_previous_changelog)
     template_release_notes_filepath = pathlib.Path(filepath_for_previous_release_notes)
+    root_dir = pathlib.Path.cwd()
 
     release_notes_content = ""
     found_entries = False
@@ -109,6 +110,17 @@ def main() -> None:
         msg = f"No unreleased entries were found in {CHANGELOG_FILE}."
         raise SystemExit(msg)
 
+    # Check for merged PRs since the last release
+    run_cmd_in_subprocess(
+        f'git config --global --add safe.directory "{root_dir.resolve().as_posix()}"'
+    )
+    commit_messages = get_commit_messages(since_tag=get_latest_tag())
+    pr_regex = re.compile(r"\(#\d+\)$")
+    pr_descriptions = "\n".join([f"- {msg}" for msg in commit_messages if pr_regex.search(msg)])
+    if not pr_descriptions and not os.getenv("UNIT_TESTING_FIND_UNRELEASED_CHANGELOG_ITEMS_ACTION"):
+        msg = "No PRs have been merged since the last release."
+        raise SystemExit(msg)
+
     # Copy the files to the correct location
     shutil.copy(CHANGELOG_FILE, template_changelog_filepath)
     with template_release_notes_filepath.open("w", encoding="utf-8") as template_release_notes:
@@ -117,14 +129,6 @@ def main() -> None:
     # If running in GitHub Actions, and the release_level is set, send the release level and
     # incoming changes to the GitHub Summary
     if release_level:
-        root_dir = pathlib.Path.cwd()
-        run_cmd_in_subprocess(
-            f'git config --global --add safe.directory "{root_dir.resolve().as_posix()}"'
-        )
-        commit_messages = get_commit_messages(since_tag=get_latest_tag())
-
-        pr_regex = re.compile(r"\(#\d+\)$")
-        pr_descriptions = "\n".join([f"- {msg}" for msg in commit_messages if pr_regex.search(msg)])
         summary_contents = (
             f"## Workflow Inputs\n- release-level: {release_level}\n"
             f"## PRs Merged Since Last Release\n{pr_descriptions}\n"
diff --git a/actions/find_unreleased_changelog_items/readme.md b/actions/find_unreleased_changelog_items/readme.md
index 23be9324..9d42251e 100644
--- a/actions/find_unreleased_changelog_items/readme.md
+++ b/actions/find_unreleased_changelog_items/readme.md
@@ -2,7 +2,9 @@
 
 This action will parse the repository's `CHANGELOG.md` file to determine if
 there are any unreleased items. It will fail if it cannot find any unreleased
-items, as this means that the package is not ready for a new release.
+items, as this means that the package is not ready for a new release. This action will also
+fail if it cannot find any merged PRs since the last release, as this also means that the
+package is not ready for a new release.
 
 This action will populate two files in the
 [`python-semantic-release` templates directory](https://python-semantic-release.readthedocs.io/en/latest/configuration.html#config-changelog-template-dir).
@@ -13,7 +15,7 @@ will be used to fill in the GitHub Release Notes.
 
 > [!IMPORTANT]
 > This action requires that the `pyproject.toml` and `CHANGELOG.md` files exist in the
-> current working directory.
+> current working directory and that all tags are fetched from the remote repository.
 
 > [!IMPORTANT]
 > This action requires the `CHANGELOG.md` file to be in a format that is based on
@@ -48,6 +50,9 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
+        with:
+          fetch-depth: 0
+          fetch-tags: true
       - uses: tektronix/python-package-ci-cd/actions/find_unreleased_changelog_items@v1.2.0
         with:
           release-level: ${{ inputs.release-level }}  # optional
diff --git a/tests/test_find_unreleased_changelog_items.py b/tests/test_find_unreleased_changelog_items.py
index b6b6f103..9ac474ee 100644
--- a/tests/test_find_unreleased_changelog_items.py
+++ b/tests/test_find_unreleased_changelog_items.py
@@ -137,6 +137,31 @@ def test_main_no_unreleased_entries(
         main()
 
 
+def test_main_with_no_merged_prs(
+    mock_env_vars: None,  # noqa: ARG001
+    mock_changelog_file: Path,  # noqa: ARG001
+    fake_process: FakeProcess,
+) -> None:
+    """Test the main function when unreleased entries are found.
+
+    Args:
+        mock_env_vars: Mock the environment variables.
+        mock_changelog_file: Mock the changelog file.
+        fake_process: The fake_process fixture, used to register commands that will be mocked.
+    """
+    fake_process.register(  # pyright: ignore[reportUnknownMemberType]
+        shlex.split("git log v1.0.0..HEAD --pretty=format:%s"),
+        stdout=b"Initial commit\n",
+    )
+    with fake_process.context() as nested_process:
+        nested_process.register(  # pyright: ignore[reportUnknownMemberType]
+            shlex.split("git log v1.0.0..HEAD --pretty=format:%s"),
+            stdout=b"Initial commit\n",
+        )
+        with pytest.raises(SystemExit, match="No PRs have been merged since the last release\\."):
+            main()
+
+
 def test_main_with_unreleased_entries(
     mock_env_vars: None,  # noqa: ARG001
     mock_changelog_file: Path,