diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 07acdf5..f42b25b 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -7,8 +7,6 @@ on: jobs: release: runs-on: docker - # Skip release commits to avoid infinite loops - if: !startsWith(github.event.head_commit.message, 'chore(release):') steps: - uses: actions/checkout@v4 with: diff --git a/scripts/release.py b/scripts/release.py index 357af3f..9dcf445 100644 --- a/scripts/release.py +++ b/scripts/release.py @@ -141,6 +141,12 @@ def main(dry_run: bool) -> None: if branch != "master": raise click.ClickException(_("Release must be run on master, currently on '{branch}'.", branch=branch)) + # Skip release commits to avoid infinite loops + last_msg = run_cmd(["git", "log", "-1", "--pretty=%B"]).stdout.strip() + if last_msg.startswith("chore(release):"): + click.echo(_("Last commit is a release commit. Nothing to do.")) + return + # Check for unreleased changes if not has_unreleased_changes(): click.echo(_("No unreleased changes found. Nothing to release.")) diff --git a/tests/unit/test_release.py b/tests/unit/test_release.py index 7a3e17e..4e503ce 100644 --- a/tests/unit/test_release.py +++ b/tests/unit/test_release.py @@ -171,12 +171,28 @@ class TestMain: @patch("scripts.release.has_unreleased_changes", return_value=False) @patch("scripts.release.run_cmd") def test_no_unreleased_changes(self, mock_run_cmd: MagicMock, mock_has: MagicMock) -> None: - mock_run_cmd.return_value = MagicMock(returncode=0, stdout="master\n", stderr="") + mock_run_cmd.side_effect = [ + MagicMock(returncode=0, stdout="master\n", stderr=""), # branch check + MagicMock(returncode=0, stdout="GRM-34 feat: something\n", stderr=""), # last commit msg + ] runner = CliRunner() result = runner.invoke(main, []) assert result.exit_code == 0 assert "No unreleased changes" in result.output + @patch.dict("os.environ", {}) + @patch("scripts.release.run_cmd") + def test_skips_release_commit(self, mock_run_cmd: MagicMock) -> None: + """Skip when last commit is a chore(release): commit to avoid loops.""" + mock_run_cmd.side_effect = [ + MagicMock(returncode=0, stdout="master\n", stderr=""), # branch check + MagicMock(returncode=0, stdout="chore(release): prepare for v0.2.0\n", stderr=""), # last commit msg + ] + runner = CliRunner() + result = runner.invoke(main, []) + assert result.exit_code == 0 + assert "release commit" in result.output + @patch.dict("os.environ", {}) @patch("scripts.release.create_and_push_tag") @patch("scripts.release.create_release_commit")