diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index d38b53f6c..bcf298dde 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -65,6 +65,9 @@ jobs: validate: uses: ./.github/workflows/validate.yml needs: eval-changes + concurrency: + group: ${{ github.workflow }}-validate-${{ github.ref_name }} + cancel-in-progress: true with: python-versions-linux: '["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]' python-versions-windows: '["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]' @@ -81,10 +84,13 @@ jobs: release: name: Semantic Release runs-on: ubuntu-latest - concurrency: push needs: validate if: ${{ needs.validate.outputs.new-release-detected == 'true' }} + concurrency: + group: ${{ github.workflow }}-release-${{ github.ref_name }} + cancel-in-progress: false + permissions: contents: write @@ -93,18 +99,21 @@ jobs: GITHUB_ACTIONS_AUTHOR_EMAIL: actions@users.noreply.github.com steps: - # Note: we need to checkout the repository at the workflow sha in case during the workflow - # the branch was updated. To keep PSR working with the configured release branches, - # we force a checkout of the desired release branch but at the workflow sha HEAD. - - name: Setup | Checkout Repository at workflow sha + # Note: We checkout the repository at the branch that triggered the workflow + # with the entire history to ensure to match PSR's release branch detection + # and history evaluation. + # However, we forcefully reset the branch to the workflow sha because it is + # possible that the branch was updated while the workflow was running. This + # prevents accidentally releasing un-evaluated changes. + - name: Setup | Checkout Repository on Release Branch uses: actions/checkout@v4 with: + ref: ${{ github.ref_name }} fetch-depth: 0 - ref: ${{ github.sha }} - - name: Setup | Force correct release branch on workflow sha + - name: Setup | Force release branch to be at workflow sha run: | - git checkout -B ${{ github.ref_name }} + git reset --hard ${{ github.sha }} - name: Setup | Download Build Artifacts uses: actions/download-artifact@v4 @@ -113,32 +122,32 @@ jobs: name: ${{ needs.validate.outputs.distribution-artifacts }} path: dist - - name: Evaluate | Determine Next Version - id: version - uses: ./ - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - root_options: "-v --noop" - - name: Release | Bump Version in Docs - if: steps.version.outputs.released == 'true' && steps.version.outputs.is_prerelease == 'false' + if: needs.validate.outputs.new-release-is-prerelease == 'false' env: - NEW_VERSION: ${{ steps.version.outputs.version }} - NEW_RELEASE_TAG: ${{ steps.version.outputs.tag }} + NEW_VERSION: ${{ needs.validate.outputs.new-release-version }} + NEW_RELEASE_TAG: ${{ needs.validate.outputs.new-release-tag }} run: | python -m scripts.bump_version_in_docs git add docs/* + - name: Evaluate | Verify upstream has NOT changed + # Last chance to abort before causing an error as another PR/push was applied to the upstream branch + # while this workflow was running. This is important because we are committing a version change + shell: bash + run: bash .github/workflows/verify_upstream.sh + - name: Release | Python Semantic Release id: release - uses: ./ + uses: python-semantic-release/python-semantic-release@v9.20.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} root_options: "-v" build: false - name: Release | Add distribution artifacts to GitHub Release Assets - uses: python-semantic-release/publish-action@v9.19.1 + uses: python-semantic-release/publish-action@v9.20.0 + if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} tag: ${{ steps.release.outputs.tag }} @@ -166,8 +175,9 @@ jobs: git push -u origin "$MAJOR_VERSION_TAG" --force outputs: - released: ${{ steps.release.outputs.released }} - tag: ${{ steps.release.outputs.tag }} + released: ${{ steps.release.outputs.released || 'false' }} + new-release-version: ${{ steps.release.outputs.version }} + new-release-tag: ${{ steps.release.outputs.tag }} deploy: @@ -187,19 +197,6 @@ jobs: id-token: write # needed for PyPI upload steps: - # Note: we need to checkout the repository at the workflow sha in case during the workflow - # the branch was updated. To keep PSR working with the configured release branches, - # we force a checkout of the desired release branch but at the workflow sha HEAD. - - name: Setup | Checkout Repository at workflow sha - uses: actions/checkout@v4 - with: - fetch-depth: 1 - ref: ${{ github.sha }} - - - name: Setup | Force correct release branch on workflow sha - run: | - git checkout -B ${{ github.ref_name }} - - name: Setup | Download Build Artifacts uses: actions/download-artifact@v4 id: artifact-download @@ -210,6 +207,8 @@ jobs: # see https://docs.pypi.org/trusted-publishers/ - name: Publish package distributions to PyPI id: pypi-publish - uses: pypa/gh-action-pypi-publish@release/v1 + uses: pypa/gh-action-pypi-publish@v1.12.4 with: + packages-dir: dist + print-hash: true verbose: true diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index 63778a3f2..6f4b50d5f 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -47,6 +47,15 @@ on: new-release-detected: description: Boolean string result for if new release is available value: ${{ jobs.build.outputs.new-release-detected }} + new-release-version: + description: Version string for the new release + value: ${{ jobs.build.outputs.new-release-version }} + new-release-tag: + description: Tag string for the new release + value: ${{ jobs.build.outputs.new-release-tag }} + new-release-is-prerelease: + description: Boolean string result for if new release is a pre-release + value: ${{ jobs.build.outputs.new-release-is-prerelease }} distribution-artifacts: description: Artifact Download name for the distribution artifacts value: ${{ jobs.build.outputs.distribution-artifacts }} @@ -91,17 +100,31 @@ jobs: python -m pip install --upgrade pip setuptools wheel pip install -e .[build] - - name: Build | Create the distribution artifacts + - name: Build | Build next version artifacts + id: version + uses: python-semantic-release/python-semantic-release@v9.20.0 + with: + github_token: "" + root_options: "-v" + build: true + changelog: true + commit: false + push: false + tag: false + vcs_release: false + + - name: Build | Annotate next version + if: steps.version.outputs.released == 'true' + run: | + printf '%s\n' "::notice::Next release will be '${{ steps.version.outputs.tag }}'" + + - name: Build | Create non-versioned distribution artifact + if: steps.version.outputs.released == 'false' + run: python -m build . + + - name: Build | Set distribution artifact variables id: build run: | - if new_version="$(semantic-release --strict version --print)"; then - printf '%s\n' "::notice::Next version will be '$new_version'" - printf '%s\n' "new_release_detected=true" >> $GITHUB_OUTPUT - semantic-release version --changelog --no-commit --no-tag - else - printf '%s\n' "new_release_detected=false" >> $GITHUB_OUTPUT - python -m build . - fi printf '%s\n' "dist_dir=dist/*" >> $GITHUB_OUTPUT printf '%s\n' "artifacts_name=dist" >> $GITHUB_OUTPUT @@ -114,7 +137,10 @@ jobs: retention-days: 2 outputs: - new-release-detected: ${{ steps.build.outputs.new_release_detected }} + new-release-detected: ${{ steps.version.outputs.released }} + new-release-version: ${{ steps.version.outputs.version }} + new-release-tag: ${{ steps.version.outputs.tag }} + new-release-is-prerelease: ${{ steps.version.outputs.is_prerelease }} distribution-artifacts: ${{ steps.build.outputs.artifacts_name }} diff --git a/.github/workflows/verify_upstream.sh b/.github/workflows/verify_upstream.sh new file mode 100644 index 000000000..3e8a38ac2 --- /dev/null +++ b/.github/workflows/verify_upstream.sh @@ -0,0 +1,33 @@ +#!/bin/bash + +set -eu +o pipefail + +# Example output of `git status -sb`: +# ## master...origin/master [behind 1] +# M .github/workflows/verify_upstream.sh +UPSTREAM_BRANCH_NAME="$(git status -sb | head -n 1 | cut -d' ' -f2 | grep -E '\.{3}' | cut -d'.' -f4)" +printf '%s\n' "Upstream branch name: $UPSTREAM_BRANCH_NAME" + +set -o pipefail + +if [ -z "$UPSTREAM_BRANCH_NAME" ]; then + printf >&2 '%s\n' "::error::Unable to determine upstream branch name!" + exit 1 +fi + +git fetch "${UPSTREAM_BRANCH_NAME%%/*}" + +if ! UPSTREAM_SHA="$(git rev-parse "$UPSTREAM_BRANCH_NAME")"; then + printf >&2 '%s\n' "::error::Unable to determine upstream branch sha!" + exit 1 +fi + +HEAD_SHA="$(git rev-parse HEAD)" + +if [ "$HEAD_SHA" != "$UPSTREAM_SHA" ]; then + printf >&2 '%s\n' "[HEAD SHA] $HEAD_SHA != $UPSTREAM_SHA [UPSTREAM SHA]" + printf >&2 '%s\n' "::error::Upstream has changed, aborting release..." + exit 1 +fi + +printf '%s\n' "Verified upstream branch has not changed, continuing with release..." diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2e21e4312..0e07247dc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -4,6 +4,29 @@ CHANGELOG ========= +.. _changelog-v9.21.0: + +v9.21.0 (2025-02-23) +==================== + +✨ Features +----------- + +* Add package name variant, ``python-semantic-release``, project script, closes `#1195`_ + (`PR#1199`_, `1ac97bc`_) + +📖 Documentation +---------------- + +* **github-actions**: Update example workflow to handle rapid merges (`PR#1200`_, `1a4116a`_) + +.. _#1195: https://github.com/python-semantic-release/python-semantic-release/issues/1195 +.. _1a4116a: https://github.com/python-semantic-release/python-semantic-release/commit/1a4116af4b999144998cf94cf84c9c23ff2e352f +.. _1ac97bc: https://github.com/python-semantic-release/python-semantic-release/commit/1ac97bc74c69ce61cec98242c19bf8adc1d37fb9 +.. _PR#1199: https://github.com/python-semantic-release/python-semantic-release/pull/1199 +.. _PR#1200: https://github.com/python-semantic-release/python-semantic-release/pull/1200 + + .. _changelog-v9.20.0: v9.20.0 (2025-02-17) diff --git a/docs/automatic-releases/github-actions.rst b/docs/automatic-releases/github-actions.rst index 6d80189c7..d67f6c590 100644 --- a/docs/automatic-releases/github-actions.rst +++ b/docs/automatic-releases/github-actions.rst @@ -337,7 +337,7 @@ before the :ref:`version ` subcommand. .. code:: yaml - - uses: python-semantic-release/python-semantic-release@v9.20.0 + - uses: python-semantic-release/python-semantic-release@v9.21.0 with: root_options: "-vv --noop" @@ -576,7 +576,7 @@ before the :ref:`publish ` subcommand. .. code:: yaml - - uses: python-semantic-release/publish-action@v9.20.0 + - uses: python-semantic-release/publish-action@v9.21.0 with: root_options: "-vv --noop" @@ -643,7 +643,7 @@ Examples Common Workflow Example ----------------------- -The following is a common workflow example that uses both the Python Semantic Release Action +The following is a simple common workflow example that uses both the Python Semantic Release Action and the Python Semantic Release Publish Action. This workflow will run on every push to the ``main`` branch and will create a new release upon a successful version determination. If a version is released, the workflow will then publish the package to PyPI and upload the package @@ -661,30 +661,74 @@ to the GitHub Release Assets as well. jobs: release: runs-on: ubuntu-latest - concurrency: release + concurrency: + group: ${{ github.workflow }}-release-${{ github.ref_name }} + cancel-in-progress: false permissions: id-token: write contents: write steps: - # Note: we need to checkout the repository at the workflow sha in case during the workflow - # the branch was updated. To keep PSR working with the configured release branches, - # we force a checkout of the desired release branch but at the workflow sha HEAD. - - name: Setup | Checkout Repository at workflow sha + # Note: We checkout the repository at the branch that triggered the workflow + # with the entire history to ensure to match PSR's release branch detection + # and history evaluation. + # However, we forcefully reset the branch to the workflow sha because it is + # possible that the branch was updated while the workflow was running. This + # prevents accidentally releasing un-evaluated changes. + - name: Setup | Checkout Repository on Release Branch uses: actions/checkout@v4 with: + ref: ${{ github.ref_name }} fetch-depth: 0 - ref: ${{ github.sha }} - - name: Setup | Force correct release branch on workflow sha + - name: Setup | Force release branch to be at workflow sha run: | - git checkout -B ${{ github.ref_name }} ${{ github.sha }} + git reset --hard ${{ github.sha }} + + - name: Evaluate | Verify upstream has NOT changed + # Last chance to abort before causing an error as another PR/push was applied to + # the upstream branch while this workflow was running. This is important + # because we are committing a version change (--commit). You may omit this step + # if you have 'commit: false' in your configuration. + # + # You may consider moving this to a repo script and call it from this step instead + # of writing it in-line. + shell: bash + run: | + set +o pipefail + + UPSTREAM_BRANCH_NAME="$(git status -sb | head -n 1 | cut -d' ' -f2 | grep -E '\.{3}' | cut -d'.' -f4)" + printf '%s\n' "Upstream branch name: $UPSTREAM_BRANCH_NAME" + + set -o pipefail + + if [ -z "$UPSTREAM_BRANCH_NAME" ]; then + printf >&2 '%s\n' "::error::Unable to determine upstream branch name!" + exit 1 + fi + + git fetch "${UPSTREAM_BRANCH_NAME%%/*}" + + if ! UPSTREAM_SHA="$(git rev-parse "$UPSTREAM_BRANCH_NAME")"; then + printf >&2 '%s\n' "::error::Unable to determine upstream branch sha!" + exit 1 + fi + + HEAD_SHA="$(git rev-parse HEAD)" + + if [ "$HEAD_SHA" != "$UPSTREAM_SHA" ]; then + printf >&2 '%s\n' "[HEAD SHA] $HEAD_SHA != $UPSTREAM_SHA [UPSTREAM SHA]" + printf >&2 '%s\n' "::error::Upstream has changed, aborting release..." + exit 1 + fi + + printf '%s\n' "Verified upstream branch has not changed, continuing with release..." - name: Action | Semantic Version Release id: release # Adjust tag with desired version if applicable. - uses: python-semantic-release/python-semantic-release@v9.20.0 + uses: python-semantic-release/python-semantic-release@v9.21.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: "github-actions" @@ -695,7 +739,7 @@ to the GitHub Release Assets as well. if: steps.release.outputs.released == 'true' - name: Publish | Upload to GitHub Release Assets - uses: python-semantic-release/publish-action@v9.20.0 + uses: python-semantic-release/publish-action@v9.21.0 if: steps.release.outputs.released == 'true' with: github_token: ${{ secrets.GITHUB_TOKEN }} @@ -706,6 +750,11 @@ to the GitHub Release Assets as well. one release job in the case if there are multiple pushes to ``main`` in a short period of time. + Secondly the *Evaluate | Verify upstream has NOT changed* step is used to ensure that the + upstream branch has not changed while the workflow was running. This is important because + we are committing a version change (``commit: true``) and there might be a push collision + that would cause undesired behavior. Review Issue `#1201`_ for more detailed information. + .. warning:: You must set ``fetch-depth`` to 0 when using ``actions/checkout@v4``, since Python Semantic Release needs access to the full history to build a changelog @@ -721,6 +770,7 @@ to the GitHub Release Assets as well. case, you will also need to pass the new token to ``actions/checkout`` (as the ``token`` input) in order to gain push access. +.. _#1201: https://github.com/python-semantic-release/python-semantic-release/issues/1201 .. _concurrency: https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idconcurrency Version Overrides Example @@ -744,7 +794,7 @@ The equivalent GitHub Action configuration would be: - name: Action | Semantic Version Release # Adjust tag with desired version if applicable. - uses: python-semantic-release/python-semantic-release@v9.20.0 + uses: python-semantic-release/python-semantic-release@v9.21.0 with: github_token: ${{ secrets.GITHUB_TOKEN }} force: patch @@ -772,13 +822,13 @@ Publish Action. .. code:: yaml - name: Release Project 1 - uses: python-semantic-release/python-semantic-release@v9.20.0 + uses: python-semantic-release/python-semantic-release@v9.21.0 with: directory: ./project1 github_token: ${{ secrets.GITHUB_TOKEN }} - name: Release Project 2 - uses: python-semantic-release/python-semantic-release@v9.20.0 + uses: python-semantic-release/python-semantic-release@v9.21.0 with: directory: ./project2 github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/pyproject.toml b/pyproject.toml index 35f8a8d04..64f665d0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta" [project] name = "python-semantic-release" -version = "9.20.0" +version = "9.21.0" description = "Automatic Semantic Versioning for Python projects" requires-python = ">=3.8" license = { text = "MIT" } @@ -39,6 +39,7 @@ dependencies = [ ] [project.scripts] +python-semantic-release = "semantic_release.__main__:main" semantic-release = "semantic_release.__main__:main" psr = "semantic_release.__main__:main" diff --git a/src/semantic_release/__init__.py b/src/semantic_release/__init__.py index dd5648aaa..fa54ef662 100644 --- a/src/semantic_release/__init__.py +++ b/src/semantic_release/__init__.py @@ -24,7 +24,7 @@ tags_and_versions, ) -__version__ = "9.20.0" +__version__ = "9.21.0" __all__ = [ "CommitParser", diff --git a/tests/e2e/test_main.py b/tests/e2e/test_main.py index 42c04b118..fc65c7f21 100644 --- a/tests/e2e/test_main.py +++ b/tests/e2e/test_main.py @@ -1,6 +1,7 @@ from __future__ import annotations import json +import subprocess from pathlib import Path from textwrap import dedent from typing import TYPE_CHECKING @@ -12,7 +13,7 @@ from semantic_release import __version__ from semantic_release.cli.commands.main import main -from tests.const import MAIN_PROG_NAME, VERSION_SUBCMD +from tests.const import MAIN_PROG_NAME, SUCCESS_EXIT_CODE, VERSION_SUBCMD from tests.fixtures.repos import repo_w_no_tags_conventional_commits from tests.util import assert_exit_code, assert_successful_exit_code @@ -25,6 +26,30 @@ from tests.fixtures.git_repo import BuiltRepoResult +@pytest.mark.parametrize( + "project_script_name", + [ + "python-semantic-release", + "semantic-release", + "psr", + ], +) +def test_entrypoint_scripts(project_script_name: str): + # Setup + command = str.join(" ", [project_script_name, "--version"]) + expected_output = f"semantic-release, version {__version__}\n" + + # Act + proc = subprocess.run( # noqa: S602, PLW1510 + command, shell=True, text=True, capture_output=True + ) + + # Evaluate + assert SUCCESS_EXIT_CODE == proc.returncode # noqa: SIM300 + assert expected_output == proc.stdout + assert not proc.stderr + + def test_main_prints_version_and_exits(cli_runner: CliRunner): cli_cmd = [MAIN_PROG_NAME, "--version"] pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy